数据结构与算法十四:赫夫曼编码

一、什么是赫夫曼编码

哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,

使用赫夫曼编码可以有效的压缩数据,通常可以节省20%~90%的空间。

在理解赫夫曼编码前,我们需要对通讯领域的两种编码方式有个粗略的了解。

假设我们需要传输 I'm a jvav programmer and I love programming这样一句话,我们有两种传输方式:

  1. 定长编码

    直接转换为对应长度的二进制格式

    01101111 00100000 00100000 00100000 00100111 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100111 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000 00100000
    

    总长度为296个字符

  2. 变长编码

    按照各个字符出现的次数进行编码,按出现次数编码,出现次数越多的,则编码越小:

    比如空格出现最多次,然后是a,以此类推......

    0=  ,1=a,10=i,11=o......
    

    当传输的信息越多的时候,变长编码实际传输的长度相对定长编码就越小

另外,我们还需要了解一下什么是补码:

计算机里面只有加法器,没有减法器,所以减法必须用加法来完成。
对于 100 以内的十进制数,“-1”就可以用"+99"代替。比如 25 - 1 = 24,可以写成 25 + 99 = (1)24。
如果限定了两位数,那“-1”和“+99”就是等效的。同样,“-2”可以用“+98”代替。

它们之间,称为补数,而100就称为

对于8位二进制数:0000 0000~1111 1111(255),模为256
-1,可以用 +255(1111 1111)代替。
-2,可以用 +254(1111 1110)代替

这些二进制数,就称为负数的补码

二、赫夫曼编码思路

同样举个例子,我们要传输 i like like like java do you like a java这段话

  1. 统计各字符的出现次数

    d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9
    
  2. 将字符出现次数作为节点的权,构建一个赫夫曼树(这里步骤同上一篇文章

    image-20200717213740072

  3. 我们使用0和1来描述某个节点在树中往左或往右的路径,比如j,从根节点出发抵达j的路径就是0000,抵达i的路径就是101

    于是现在对所有字符的路径进行统计,就有:

    o: 1000     u: 10010     d: 100110     y: 100111    i: 101    a : 110
    k: 1110     e: 1111      j: 0000       v: 0001      l: 001      : 01
    
  4. 按照上面的路径,我们将其转为二进制

    1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
    

    直接转为二进制长度为359,而经过赫夫曼编码长度则是133,与直接转为二进制相比,缩短了62.9%

三、代码实现

1.创建节点类

/**
 * @Author:CreateSequence
 * @Date:2020-07-18 13:28
 * @Description:赫夫曼编码用节点
 */
public class HuffmanCodeNode implements Comparable<HuffmanCodeNode>{

    //字符
    Byte data;
    //权值
    int weight;
    HuffmanCodeNode left;
    HuffmanCodeNode right;

    public HuffmanCodeNode(Byte data,int weight) {
        this.data = data;
        this.weight = weight;
    }

    public HuffmanCodeNode(HuffmanCodeNode left,HuffmanCodeNode right) {
        //计算子节点权值之和
        this.weight = left.weight + right.weight;
        this.left = left;
        this.right = right;
    }

    @Override
    public int compareTo(HuffmanCodeNode o) {
        //从小到大排序
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "{" +
            "data=" + data +
            ",weight=" + weight +
            '}';
    }
    
}

2.统计字符出现次数,并组装节点列表

对应思路中的第一步:

/**
 * 统计字符在字符串中的出现次数,并组装节点列表
 * @param str 字符串
 * @return
 */
private List<HuffmanCodeNode> getNodes(String str) {
    //将字符串转为字符串数组
    byte[] strBytes = str.getBytes();

    //遍历字节数组,并且统计某一字符出现次数
    Map<Byte,Integer> counts = new HashMap<>(24);
    for (byte b : bytes) {
        Integer count = counts.get(b);
        //判断某一字符是否第一次出现
        if (count == null) {
            counts.put(b,1);
        }else {
            //不是就让出现次数+1
            counts.put(b,count ++);
        }
    }

    //将map转为节点集合
    List<HuffmanCodeNode> nodes = new ArrayList<>();
    for (Map.Entry<Byte,Integer> entry : counts.entrySet()) {
        nodes.add(new HuffmanCodeNode(entry.getKey(),entry.getValue()));
    }

    return nodes;
}

3.生成赫夫曼树

对应思路中的第二步:

/**
 * 构建赫夫曼树
 * @param nodes 节点集合
 * @return 最终生成的树的根节点
 */
private HuffmanCodeNode createTree(List<HuffmanCodeNode> nodes) {
    //构建树
    while (nodes.size() > 1) {
        //从小到大排序
        Collections.sort(nodes);
        //取出最小的两个数构建树
        HuffmanCodeNode left = nodes.get(0);
        HuffmanCodeNode right = nodes.get(1);
        HuffmanCodeNode parant = new HuffmanCodeNode(left,right);
        //删除两个节点
        nodes.remove(left);
        nodes.remove(right);
        //将根节点添加至集合
        nodes.add(parant);
    }
    //返回树的根节点
    return nodes.get(0);
}

当然,这个时候可以通过前序遍历来检查是否构建成功

/**
 * 前序遍历
 * @param node 树的根节点
 */
private void preOrder(HuffmanCodeNode node) {
    //递归
    System.out.println(node.toString());
    if (node.left != null) {
        preOrder(node.left);
    }
    if (node.right != null) {
        preOrder(node.right);
    }
}

4.得到赫夫曼编码

对应思路中的第三步:

我们已经得到了赫夫曼树,现在我们需要获得从根节点到各个叶子结点的路径,也就是赫夫曼编码

/**
 * 生成赫夫曼树对应的赫夫曼编码集合
 */
private Map<Byte,String> huffmanCodes = new HashMap<>();
/**
 * 储存某个叶子节点的拼接路径
 */
private StringBuilder stringBuilder = new StringBuilder();

/**
 * 将传入的节点作为树的根节点,找到其所有的叶子结点的赫夫曼编码,并放入赫夫曼编码集合
 * @param node 节点
 * @param way 叶子结点的路径,左为0,右为1
 * @param builder 用于拼接路径
 */
private Map<Byte,String> getCodes(HuffmanCodeNode node,String way,StringBuilder builder) {
    StringBuilder stringBuilder = new StringBuilder(builder);
    //建路径拼接至上一路径
    stringBuilder.append(way);
    if (node != null) {
        //判断当前是否为叶子节点
        if (node.data == null) {
            //向左右递归直到找到叶子结点
            getCodes(node.left,"0",stringBuilder);
            getCodes(node.right,"1",stringBuilder);
        }else {
            //已经是叶子结点,将路径存入集合
            huffmanCodes.put(node.data,stringBuilder.toString());
        }
    }
    return huffmanCodes;
}

public Map<Byte,String> getCodes() {
    //构建赫夫曼树
    HuffmanCodeNode root = createTree();
    //处理左右子树
    getCodes(root.left,stringBuilder);
    getCodes(root.right,stringBuilder);
    //返回赫夫曼编码
    return huffmanCodes;
}

5.将得到的赫夫曼编码转回字节数组

对应思路中的第四步,也就是最后一步:

我们得到了赫夫曼编码表,也就是这玩意: Map<Byte,String> huffmanCodes,每串赫夫曼编码字符串都对应一个字符,我们需要处理赫夫曼编码的每一个字符,将其转为二进制后再转为byte,最后处理完得到一队字节数组。

/**
 * 将字符串对应的byte数组,转换为经过赫夫曼编码压缩后的byte数组
 * @param bytes
 * @param huffmanCodes
 * @return
 */
private byte[] zip(byte[] bytes,Map<Byte,String> huffmanCodes) {
    //获取赫夫曼编码
    StringBuilder stringBuilder = new StringBuilder();
    //遍历byte数组,一个byte表示一个字符
    for (Byte b : bytes) {
        //将字符转为赫夫曼编码格式,一个字符对应8位编码
        stringBuilder.append(huffmanCodes.get(b));
    }

    //一个字符对应对应的8位的赫夫曼编码,如果赫夫曼编码无法被8整除,就直接补齐赫夫曼编码不足八位的那一个字符
    int len = stringBuilder.length() % 8 == 0 ? stringBuilder.length() / 8 : stringBuilder.length() / 8 + 1;
    //System.out.println("有几个字符:"+len);

    //将压缩后的赫夫曼编码按字符分开存储
    byte[] huffmanCodeBytes = new byte[len];
    //计录已处理几个字符
    int index = 0;
    //每8位编码对应一个byte,所以步长为8
    //每循环一次处理一个byte,也就是一个字符
    for (int i = 0; i < stringBuilder.length(); i += 8) {
        String strBytes;
        //判断编码长度是否超过8位
        if (i + 8 < stringBuilder.length()) {
            //超过8位就从赫夫曼编码截取八位(也就是一个字符)
            strBytes = stringBuilder.substring(i,i + 8);
        }else {
            //否则就有多少截多少
            strBytes = stringBuilder.substring(i);
        }
        //将赫夫曼编码转为二进制,存入byte数组
        huffmanCodeBytes[index] = (byte) Integer.parseInt(strBytes,2);

        //位已处理字符数+1
        index++;
    }

    //循环结束后,返回赫夫曼编码按字符转换得到的字节数组
    return huffmanCodeBytes;
}

public byte[] zip() {
    byte[] bytes = str.getBytes();
    Map<Byte,String> huffmanCodes = getCodes();
    return zip(bytes,huffmanCodes);
}

6.解码

信息被赫夫曼编码处理后我们会得到一队字节数组,如果要解码,我们需要先把字节数组按字符一个字节一个字节的转为二进制,然后通过赫夫曼编码表把二进制和字符字节一一找出:

/**
 * 将byte转成二进制字符串
 * @param isComple 是否需要补高位。最后一个字节无需补位
 * @param b 要转换的字节
 * @return
 */
private String byteToString(boolean isComplate,byte b) {
    int temp = b;
    //判断是否需要补齐高位
    if (isComplate) {
        temp |= 256;
    }
    //返回temp对应的二进制补码
    String str = Integer.toBinaryString(temp);
    return isComplate ? str.substring(str.length() - 8) : str;
}

/**
 * 解码
 * @param huffmanCodes 赫夫曼编码表
 * @param huffmanBytes 赫夫曼编码处理过的字节数组
 * @return 原来未被转为赫夫曼编码的的字符串字节素组
 */
private byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanBytes) {

    //将赫夫曼编码处理过byte数组转为二进制字符串
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < huffmanBytes.length; i++) {

        boolean isComplate = true;
        //如果是最后一个字节就不用补高位了
        if (i == huffmanBytes.length - 1) {
            isComplate = false;
        }
        //拼接字节转的二进制字符串
        stringBuilder.append(byteToString(isComplate,huffmanBytes[i]));
    }

    //把字符串按照指定赫夫曼编码进行解码
    //原本赫夫曼编码表是<字节,二进制字符串>,现在要转为<二进制字符串,字节>以通过转换得到的二进制字符串取出对应的字节
    Map<String,Byte> reHuffmanCodes = new HashMap<>();
    for (Map.Entry<Byte,String> entry : huffmanCodes.entrySet()) {
        reHuffmanCodes.put(entry.getValue(),entry.getKey());
    }

    List<Byte> bytes = new ArrayList<>();
    //由于无法确认拼接后的二进制字符串每八位一定就能和某个字节对应,所以需要进行字符串匹配
    //这里可以简单理解为双指针,一号指针从i开始,二号指针从i+1开始
    //一号指针先指向字符串第i字符,然后二号指针从i+1个字符开始不断后移,然后进行进行匹配
    //比如:i=0,j=1,第一次截取并匹配的字符就是[0,1),也就是0;第二次是[0,2),也就是01;然后是[0,3).....以此类推
    //直到找到以后,比如[2,7),就移动一号指针到7,二号指针移动到8
    for (int i = 0,j = 1; i < stringBuilder.length(); i = --j) {
        String key = "";
        while (!reHuffmanCodes.containsKey(key)) {
            key = stringBuilder.substring(i,j);
            j++;
        }
        bytes.add(reHuffmanCodes.get(key));
    }

    //由集合转为字节数组
    byte b[] = new byte[bytes.size()];
    for (int i = 0; i < b.length; i++) {
        b[i] = bytes.get(i);
    }

    return b;
}

四、完整代码

具体代码和测试用例可以去GitHub上看,这里就放实现类:

import java.util.*;

/**
 * @Author:CreateSequence
 * @Date:2020-07-18 13:26
 * @Description:赫夫曼编码
 */
public class HuffmanCode {

    private String str;

    public String getStr() {
        return str;
    }

    public HuffmanCode(String code) {
        if (code.length() == 0 || code == null) {
            throw new RuntimeException("字符串必须有至少一个字符!");
        }
        this.str = code;
    }

    /**
     * 统计字符在字符串中的出现次数,并组装节点列表
     * @return
     */
    public List<HuffmanCodeNode> getNodes() {
        //将字符串转为字符串数组
        byte[] bytes = str.getBytes();

        //遍历字节数组,并且统计某一字符出现次数
        Map<Byte,Integer> counts = new HashMap<>(24);
        for (byte b : bytes) {
            Integer count = counts.get(b);
            //判断某一字符是否第一次出现
            if (count == null) {
                counts.put(b,1);
            }else {
                //不是就让出现次数+1
                counts.put(b,count ++);
            }
        }

        //将map转为节点集合
        List<HuffmanCodeNode> nodes = new ArrayList<>();
        for (Map.Entry<Byte,Integer> entry : counts.entrySet()) {
            nodes.add(new HuffmanCodeNode(entry.getKey(),entry.getValue()));
        }

        return nodes;
    }

    /**
     * 构建赫夫曼树
     * @param nodes
     * @return
     */
    private HuffmanCodeNode createTree(List<HuffmanCodeNode> nodes) {
        //构建树
        while (nodes.size() > 1) {
            //从小到大排序
            Collections.sort(nodes);
            //取出最小的两个数构建树
            HuffmanCodeNode left = nodes.get(0);
            HuffmanCodeNode right = nodes.get(1);
            HuffmanCodeNode parant = new HuffmanCodeNode(left,right);
            //删除两个节点
            nodes.remove(left);
            nodes.remove(right);
            //将根节点添加至集合
            nodes.add(parant);
        }
        //返回树的根节点
        return nodes.get(0);
    }
    public HuffmanCodeNode createTree(){
        return createTree(getNodes());
    }

    /**
     * 前序遍历
     * @param node 树的根节点
     */
    private void preOrder(HuffmanCodeNode node) {
        //递归
        System.out.println(node.toString());
        if (node.left != null) {
            preOrder(node.left);
        }
        if (node.right != null) {
            preOrder(node.right);
        }
    }
    public void preOrder(){
        HuffmanCodeNode root = createTree(getNodes());
        preOrder(root);
    }

    /**
     * 生成赫夫曼树对应的赫夫曼编码集合
     */
    private Map<Byte,String> huffmanCodes = new HashMap<>();
    /**
     * 储存某个叶子节点的拼接路径
     */
    private StringBuilder stringBuilder = new StringBuilder();

    /**
     * 将传入的节点作为树的根节点,找到其所有的叶子结点的赫夫曼编码,并放入赫夫曼编码集合
     * @param node 节点
     * @param way 叶子结点的路径,左为0,右为1
     * @param builder 用于拼接路径
     */
    private Map<Byte,StringBuilder builder) {
        StringBuilder stringBuilder = new StringBuilder(builder);
        //建路径拼接至上一路径
        stringBuilder.append(way);
        if (node != null) {
            //判断当前是否为叶子节点
            if (node.data == null) {
                //向左右递归直到找到叶子结点
                getCodes(node.left,stringBuilder);
                getCodes(node.right,stringBuilder);
            }else {
                //已经是叶子结点,将路径存入集合
                huffmanCodes.put(node.data,stringBuilder.toString());
            }
        }
        //返回赫夫曼编码
        return huffmanCodes;
    }

    public Map<Byte,String> getCodes() {
        //构建赫夫曼树
        HuffmanCodeNode root = createTree();
        //处理左右子树
        getCodes(root.left,stringBuilder);
        getCodes(root.right,stringBuilder);
        //返回赫夫曼编码
        return huffmanCodes;
    }

    /**
     * 将字符串对应的byte数组,转换为经过赫夫曼编码压缩后的byte数组
     * @param bytes
     * @param huffmanCodes
     * @return
     */
    private byte[] zip(byte[] bytes,String> huffmanCodes) {
        //获取赫夫曼编码
        StringBuilder stringBuilder = new StringBuilder();
        //遍历byte数组,一个byte表示一个字符
        for (Byte b : bytes) {
            //将字符转为赫夫曼编码格式,一个字符对应8位编码
            stringBuilder.append(huffmanCodes.get(b));
        }

        //一个字符对应对应的8位的赫夫曼编码,如果赫夫曼编码无法被8整除,就直接补齐赫夫曼编码不足八位的那一个字符
        int len = stringBuilder.length() % 8 == 0 ? stringBuilder.length() / 8 : stringBuilder.length() / 8 + 1;
        //System.out.println("有几个字符:"+len);

        //将压缩后的赫夫曼编码按字符分开存储
        byte[] huffmanCodeBytes = new byte[len];
        //计录已处理几个字符
        int index = 0;
        //每8位编码对应一个byte,所以步长为8
        //每循环一次处理一个byte,也就是一个字符
        for (int i = 0; i < stringBuilder.length(); i += 8) {
            String strBytes;
            //判断编码长度是否超过8位
            if (i + 8 < stringBuilder.length()) {
                //超过8位就从赫夫曼编码截取八位(也就是一个字符)
                strBytes = stringBuilder.substring(i,i + 8);
            }else {
                //否则就有多少截多少
                strBytes = stringBuilder.substring(i);
            }
            //将赫夫曼编码转为二进制,存入byte数组
            huffmanCodeBytes[index] = (byte) Integer.parseInt(strBytes,2);

            //位已处理字符数+1
            index++;
        }

        //循环结束后,返回赫夫曼编码按字符转换得到的字节数组
        return huffmanCodeBytes;
    }
    public byte[] zip() {
        byte[] bytes = str.getBytes();
        Map<Byte,String> huffmanCodes = getCodes();
        return zip(bytes,huffmanCodes);
    }

    /**
     * 将byte转成二进制字符串
     * @param isComple 是否需要补高位。最后一个字节无需补位
     * @param b 要转换的字节
     * @return
     */
    private String byteToString(boolean isComplate,byte b) {
        int temp = b;
        //判断是否需要补齐高位
        if (isComplate) {
            temp |= 256;
        }
        //返回temp对应的二进制补码
        String str = Integer.toBinaryString(temp);
        return isComplate ? str.substring(str.length() - 8) : str;
    }

    /**
     * 解码
     * @param huffmanCodes 赫夫曼编码表
     * @param huffmanBytes 赫夫曼编码处理过的字节数组
     * @return 原来未被转为赫夫曼编码的的字符串字节素组
     */
    private byte[] decode(Map<Byte,byte[] huffmanBytes) {

        //将赫夫曼编码处理过byte数组转为二进制字符串
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < huffmanBytes.length; i++) {

            boolean isComplate = true;
            //如果是最后一个字节就不用补高位了
            if (i == huffmanBytes.length - 1) {
                isComplate = false;
            }
            //拼接字节转的二进制字符串
            stringBuilder.append(byteToString(isComplate,huffmanBytes[i]));
        }

        //把字符串按照指定赫夫曼编码进行解码
        //原本赫夫曼编码表是<字节,二进制字符串>,现在要转为<二进制字符串,字节>以通过转换得到的二进制字符串取出对应的字节
        Map<String,Byte> reHuffmanCodes = new HashMap<>();
        for (Map.Entry<Byte,String> entry : huffmanCodes.entrySet()) {
            reHuffmanCodes.put(entry.getValue(),entry.getKey());
        }

        List<Byte> bytes = new ArrayList<>();
        //由于无法确认拼接后的二进制字符串每八位一定就能和某个字节对应,所以需要进行字符串匹配
        //这里可以简单理解为双指针,一号指针从i开始,二号指针从i+1开始
        //一号指针先指向字符串第i字符,然后二号指针从i+1个字符开始不断后移,然后进行进行匹配
        //比如:i=0,j=1,第一次截取并匹配的字符就是[0,3).....以此类推
        //直到找到以后,比如[2,7),就移动一号指针到7,二号指针移动到8
        for (int i = 0,j = 1; i < stringBuilder.length(); i = --j) {
            String key = "";
            while (!reHuffmanCodes.containsKey(key)) {
                key = stringBuilder.substring(i,j);
                j++;
            }
            bytes.add(reHuffmanCodes.get(key));
        }

        //由集合转为字节数组
        byte b[] = new byte[bytes.size()];
        for (int i = 0; i < b.length; i++) {
            b[i] = bytes.get(i);
        }

        return b;
    }

    public byte[] decode(byte[] huffmanBytes) {
        return decode(huffmanCodes,huffmanBytes);
    }

}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


背景:计算机内部用补码表示二进制数。符号位1表示负数,0表示正数。正数:无区别,正数 的原码= 反码 = 补码重点讨论负数若已知 负数 -8,则其原码为:1000 1000,(1为符号位,为1代表负数,为0代表正数)反码为:1111 0111,(符号位保持不变,其他位置按位取反)补码为:1111 1000,(反码 + 1)即在计算机中 用 1111 1000表示 -8若已知补码为 1111 1000,如何求其原码呢?(1)方法1:求负数 原码---&gt;补...
大家好,我们现在来讲解关于加密方面的知识,说到加密我认为不得不提MD5,因为这是一种特殊的加密方式,它到底特殊在哪,现在我们就开始学习它全称:message-digest algorithm 5翻译过来就是:信息 摘要 算法 5加密和摘要,是不一样的加密后的消息是完整的;具有解密算法,得到原始数据;摘要得到的消息是不完整的;通过摘要的数据,不能得到原始数据;所以,当看到很多人说,md5,加密,解密的时候,呵呵一笑就好了。MD5长度有人说md5,128位,32位,16位,到
相信大家在大学的《算法与数据结构》里面都学过快速排序(QuickSort), 知道这种排序的性能很好,JDK里面直到JDK6用的都是这种经典快排的算法。但是到了JDK7的时候JDK内置的排序算法已经由经典快排变成了Dual-Pivot排序算法。那么Dual-Pivot到底是何方圣神,能比我们学过的经典快排还要快呢?我们一起来看看。经典快排在学习新的快排之前,我们首先来复习一下经典快排,它的核心思想是:接受一个数组,挑一个数(pivot),然后把比它小的那一摊数放在它的左边,把比它大的那一摊数放
加密在编程中的应用的是非常广泛的,尤其是在各种网络协议之中,对称/非对称加密则是经常被提及的两种加密方式。对称加密我们平时碰到的绝大多数加密就是对称加密,比如:指纹解锁,PIN 码锁,保险箱密码锁,账号密码等都是使用了对称加密。对称加密:加密和解密用的是同一个密码或者同一套逻辑的加密方式。这个密码也叫对称秘钥,其实这个对称和不对称指的就是加密和解密用的秘钥是不是同一个。我在上大学的时候做过一个命令行版的图书馆管理系统作为 C 语言课设。登入系统时需要输入账号密码,当然,校验用户输入的密码
前言我的目标是写一个非常详细的关于diff的干货,所以本文有点长。也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角。先来了解几个点...1. 当数据发生变化时,vue是怎么更新节点的?要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们。我们先根据真实DOM生成一颗virtual DOM,当v
对称加密算法 所有的对称加密都有一个共同的特点:加密和解密所用的密钥是相同的。现代对称密码可以分为序列密码和分组密码两类:序列密码将明文中的每个字符单独加密后再组合成密文;而分组密码将原文分为若干个组,每个组进行整体加密,其最终加密结果依赖于同组的各位字符的具体内容。也就是说,分组加密的结果不仅受密钥影响,也会受到同组其他字符的影响。序列密码分组密码序列密码的安全性看上去要更弱一些,但是由于序列密码只需要对单个位进行操作,因此运行速度比分组加密要快...
本文介绍RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题,对第一次接触RSA的开发人员来讲,RSA算是比较复杂的算法,RSA算法自己其实也很简单,RSA的复杂度是由于数学家把效率和安全也考虑进去的缘故。html本文先只谈密钥长度、明文长度和密文长度的概念知识,RSA的理论及示例等之后再谈。提到密钥,咱们不得不提到RSA的三个重要大数:公钥指数e、私钥指数d和模值n。这三个大数是咱们使用RSA时须要直接接触的,理解了本文的基础概念,即便未接触过RSA的开发人员也能应对自如的使用RSA相关函数库,
直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。算法:1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为03. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为14. 判断某个key是否在集合时
你会用什么样的算法来为你的用户保存密码?如果你还在用明码的话,那么一旦你的网站被hack了,那么你所有的用户口令都会被泄露了,这意味着,你的系统或是网站就此完蛋了。所以,我们需要通过一些不可逆的算法来保存用户的密码。比如:MD5, SHA1, SHA256, SHA512, SHA-3,等Hash算法。这些算法都是不可逆的。系统在验证用户的口令时,需要把Hash加密过后的口令与后面存放口令的数据库中的口令做比较,如果一致才算验证通过。但你觉得这些算法好吗?我说的是:MD5, SHA1, SHA256,
在日常工作中经常会使用excel,有时在表格中需要筛选出重复的数据,该怎么操作呢?1、以下图中的表格数据为例,筛选出列中重复的内容;2、打开文件,选中需要筛选的数据列,依次点击菜单项【开始】-【条件格式】-【突出显示单元格规则】-【重复值】;3、将重复的值突出颜色显示;4、选中数据列,点击【数据】-【筛选】;5、点击列标题的的下拉小三角,点击【按颜色筛选】,即可看到重复的数据;...
工作中经常有和第三方机构联调接口的事情,顾将用到过的做以记录。 在和第三方联调时,主要步骤为:网络、加解密/签名验签、接口数据等,其中接口数据没啥好说的。 在联调前就需要先将两边的网络连通,一般公司的生产环境都加了防火墙,测试环境有的是有防火墙,有的则没有防火墙,这个需要和第三方人员沟通,如果有防火墙的就需要将我们的出口ip或域名发送给第三方做配置,配置了之后网络一般都是通的。加解密与签名验签: 一般第三方公司都会有加解密或签名验签的,毕竟为了数据安全。一般就是三...
此文章不包含认证机制。任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过某种方式进行加密。如今已有很多标准的算法比如SHA或者MD5再结合salt(盐)使用是一个不错的选择。废话不多说!直接开始SpringBoot 中提供了Spring Security:BCryptPasswordEncoder类,实现Spring的PasswordEncoder接口使用BCrypt强哈希方法来加密密码。第一步:pom导入依赖:&lt;dependency&gt; &lt;groupId...
前言在所有的加密算法中使用最多的就是哈希加密了,很多人第一次接触的加密算法如MD5、SHA1都是典型的哈希加密算法,而哈希加密除了用在密码加密上,它还有很多的用途,如提取内容摘要、生成签名、文件对比、区块链等等。这篇文章就是想详细的讲解一下哈希加密,并分享一个哈希加密的工具类。概述哈希函数(Hash Function),也称为散列函数或杂凑函数。哈希函数是一个公开函数,可以将任意长度的消息M映射成为一个长度较短且长度固定的值H(M),称H(M)为哈希值、散列值(Hash Value)、杂凑值或者消息
#快速排序解释 快速排序 Quick Sort 与归并排序一样,也是典型的分治法的应用。 (如果有对 归并排序还不了解的童鞋,可以看看这里哟~ 归并排序)❤❤❤ ###快速排序的分治模式 1、选取基准
#堆排序解释 ##什么是堆 堆 heap 是一种近似完全二叉树的数据结构,其满足一下两个性质 1. 堆中某个结点的值总是不大于(或不小于)其父结点的值; 2. 堆总是一棵完全二叉树 将根结点最大的堆叫
#前言 本文章是建立在插入排序的基础上写的喔,如果有对插入排序还有不懂的童鞋,可以看看这里。 ❤❤❤ 直接/折半插入排序 2路插入排序 ❤❤❤ #希尔排序解释 希尔排序 Shell Sort 又名&q
#归并排序解释 归并排序 Merge Sort 是典型的分治法的应用,其算法步骤完全遵循分治模式。 ##分治法思想 分治法 思想: 将原问题分解为几个规模较小但又保持原问题性质的子问题,递归求解这些子
#前言 本文章是建立在冒泡排序的基础上写的,如还有对 冒泡排序 不了解的童鞋,可以看看这里哦~ 冒泡排序 C++ #双向冒泡排序原理 双向冒泡排序 的基本思想与 冒泡排序还是一样的。冒泡排序 每次将相
#插入排序解释 插入排序很好理解,其步骤是 :先将第一个数据元素看作是一个有序序列,后面的 n-1 个数据元素看作是未排序序列。对后面未排序序列中的第一个数据元素在这个有序序列中进行从后往前扫描,找到
#桶排序解释 ##桶排序思想 桶排序 是一种空间换取时间的排序方式,是非基于比较的。 桶排序 顾名思义,就是构建多个映射数据的桶,将数据放入桶内,对每个桶内元素进行单独排序。假设我们有 n 个待排序的