代码实现
1.节点
package HuffmanCode;
public class huffmanNodes implements Comparable<huffmanNodes> {
private Byte data; // 存数据 比如,a-->97
private int weight; // 数据的权值,这里是字符出现的次数
private huffmanNodes left; // 节点的左指针
private huffmanNodes right; // 节点的右指针
public huffmanNodes() {
}
public huffmanNodes(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
public Byte getData() {
return data;
}
public void setData(Byte data) {
this.data = data;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public huffmanNodes getLeft() {
return left;
}
public void setLeft(huffmanNodes left) {
this.left = left;
}
public huffmanNodes getRight() {
return right;
}
public void setRight(huffmanNodes right) {
this.right = right;
}
// 前序遍历
public void PreOrder() {
System.out.println(this);
if (this.left != null) {
this.left.PreOrder();
}
if (this.right != null) {
this.right.PreOrder();
}
}
@Override
public String toString() {
return "{" +
"data=" + data +
", weight=" + weight +
'}';
}
@Override
public int compareTo(huffmanNodes o) {
// 升序排序
return this.weight - o.weight;
}
}
2.赫夫曼编码
package HuffmanCode;
import java.util.*;
/**
* @program: test01
* @description: 测试
* @author: Zhou Jian
* @create: 2020-07-27 16:09
*/
public class Input {
// 定义一个Map<Byte,String>,存放赫夫曼编码表
// 形式: a 97-01.........
static Map<Byte, String> HashManCodes = new HashMap<>();
// 定义StringBuilder,用于拼接路径,存放叶子结点的路径
static StringBuilder stringBuilder = new StringBuilder();
public static void main(String[] args) {
System.out.println("===================================编码=========================================");
/*
1.压缩数据 ---> 00010001000000.................
*/
// 定义一个要编码的字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入字符串:");
String str = sc.nextLine();
// 将其放入字节数组中
byte[] bytesArr = str.getBytes();
System.out.println("编码之前,要发送的内容是:" + str + ",对应的字节数组是:" + new String(bytesArr) + ",长度为:" + bytesArr.length);
// 得到赫夫曼编码的字符数组 00010001000000.................
byte[] zip = huffmanZip(bytesArr);
System.out.println("编码之后,要发送的内容是:" + Arrays.toString(zip) + ",长度为:" + zip.length);
double d = (double) zip.length / (double) bytesArr.length;
System.out.printf("压缩率大概是:%.2f", d); // 压缩率大概0.5
System.out.println();
System.out.println("===================================解码=========================================");
/*
2.解压数据
*/
byte[] decode = decode(HashManCodes, zip);
System.out.println("解码后的字节数组是:" + new String(decode));
}
/**
* 使用一个方法,将所写的方法封装起来,便于调用
* 数据压缩
*
* @param bytes 原始的字符串对应的字节数组
* @return 经过赫夫曼编码处理后的字节数组(压缩后的数组)
*/
public static byte[] huffmanZip(byte[] bytes) {
// 处理字节数组
List<huffmanNodes> nodes = getNodes(bytes);
// 创建赫夫曼树
huffmanNodes root = HuffManTreeBuild(nodes);
// 生成对应的赫夫曼编码(每个字节的0101编码)
Map<Byte, String> codes = getCodes(root);
// 根据生成的赫夫曼编码,得到压缩后的赫夫曼编码字节数组
byte[] zipArr = zip(bytes, codes);
return zipArr;
}
/* 一.压缩数据 */
/**
* 1.统计每个字符出现的次数
*
* @param byteArr 存储字符的字符数组
* @return 存储节点的列表
*/
public static List<huffmanNodes> getNodes(byte[] byteArr) {
// 存储节点的列表
ArrayList<huffmanNodes> nodes = new ArrayList<>();
// 遍历字符数组,统计次数 ---> 使用map存储
HashMap<Byte, Integer> obj = new HashMap<>();
// 循环遍历字符数组
// b ---> 取出的字符
for (byte b : byteArr) {
// 统计每个字符出现的次数
Integer counts = obj.get(b);
// 如果没有出现过,则将其值置为1
if (counts == null) {
obj.put(b, 1);
} else { // 否则,自加1
obj.put(b, counts + 1);
}
}
// 将每个键值对转化为Node节点,并添加到集合中
for (Map.Entry<Byte, Integer> key_Value : obj.entrySet()) {
nodes.add(new huffmanNodes(key_Value.getKey(), key_Value.getValue()));
}
return nodes;
}
/**
* 2.构造赫夫曼树
*
* @param m 要排序的集合
* @return 根节点
*/
public static huffmanNodes HuffManTreeBuild(List<huffmanNodes> m) {
while (m.size() > 1) {
// 升序排序
Collections.sort(m);
// 处理的过程是个循环的过程
// 开始创建赫夫曼树
// 从集合中找出权值最小的节点
huffmanNodes leftNode = m.get(0);
// 从集合中找到权值次小的节点
huffmanNodes rightNode = m.get(1);
// 找到之后,产生其父节点
huffmanNodes parent = new huffmanNodes(null, leftNode.getWeight() + rightNode.getWeight());
// 重构父子关系
parent.setLeft(leftNode);
parent.setRight(rightNode);
// 从集合中删除已经处理过的节点
m.remove(leftNode);
m.remove(rightNode);
// 将新的父节点添加到集合中
m.add(parent);
}
return m.get(0);
}
/**
* 3.为了调用方便,这里重载getCodes方法
*
* @param root 传入的要处理的节点
* @return 赫夫曼编码表
*/
public static Map<Byte, String> getCodes(huffmanNodes root) {
if (root == null) {
return null;
}
// 递归左子节点
getCodes(root.getLeft(), "0", stringBuilder);
// 递归右子节点
getCodes(root.getRight(), "1", stringBuilder);
return HashManCodes;
}
/**
* 3.将传入的节点的所有叶子结点的赫夫曼编码得到,并存放到HashManCodes集合中
* 例如:97-001 78-010 ...................
*
* @param node 传入的节点
* @param path 路径,左子节点---->0,右子节点---->1
* @param stringBuilder 拼接路径
*/
public static void getCodes(huffmanNodes node, String path, StringBuilder stringBuilder) {
// 构造StringBuilder,用于存放结点的路径
StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
// 将路径添加到集合中
stringBuilder2.append(path);
// 生成节点的路径
if (node != null) {
if (node.getData() == null) { // 不是叶子结点(使用赫夫曼树生成的节点)
// 左子节点递归
getCodes(node.getLeft(), "0", stringBuilder2);
// 右子节点递归
getCodes(node.getRight(), "1", stringBuilder2);
} else { // 是叶子结点,就直接添加到赫夫曼map中
HashManCodes.put(node.getData(), stringBuilder2.toString());
}
}
}
/**
* 4.编写一个方法,将字符串对应的byte[]数组,通过生成的赫夫曼编码表,返回一个处理过后的字节数组
* 例如:010000000000001010000000000000000001..................
*
* @param bytes 原始的字符串对应的byte数组
* @param huffmanCodes 生成的赫夫曼编码
* @return 处理后的byte数组
*/
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
// 1.利用赫夫曼编码表将byte数组转换为赫夫曼编码后对应的字符串
StringBuilder stringBuilder = new StringBuilder();
// 2.遍历byte数组
for (byte b : bytes) {
// 取出byte的每一个字符,并转换为对应的赫夫曼编码,添加到StringBuilder中
stringBuilder.append(huffmanCodes.get(b));
}
System.out.println("原始数据的字节数组对应的赫夫曼编码为:" + stringBuilder.toString());
// 3.将00110110010101101000011111110011转换为byte数组
// 统计byte数组的长度(便于截取--->8位一组)
int length;
if (stringBuilder.length() % 8 == 0) {
length = stringBuilder.length() / 8;
} else {
length = stringBuilder.length() / 8 + 1;
}
// 创建 存储压缩后的byte数组
byte[] by = new byte[length];
// 记录第几个byte
int index = 0;
// 步长为8(8位一组)
for (int i = 0; i < stringBuilder.length(); i += 8) {
// 存放8位0101编码,不够时,就存储剩余几位
String strByte;
// 防止越界
if (i + 8 > stringBuilder.length()) {
// 取出剩下几位,i-i+8位
strByte = stringBuilder.substring(i);
} else {
// 每循环1次,从stringBuilder中取出8位,存放在strByte中
strByte = stringBuilder.substring(i, i + 8);
}
// 将strByte转换为一个byte,存放到by中
by[index] = (byte) Integer.parseInt(strByte, 2);
index++;
}
return by;
}
/* 二.解压数据
<1>将得到的字节数组[97, -22, -69, 60, -118, -57, 21, 56, 126, 105, -83, -54, -77, -13, 68, -50, -71, -112, 92,-21, 1]
转化为赫夫曼编码对应的字符数组 -------> "00100000000010......................."
<2>将得到的字符数组,通过查看赫夫曼编码表,得到原始的字符串 ---------> I love you
*/
/**
* 计算出所给字节的二进制编码(单个字节)
*
* @param flag 判断是否要高位补位
* @param str 需要转换的字节
* @return 转换之后的二进制编码(补码)
*/
public static String byteToBinaryCodeString(boolean flag, byte str) {
// 将字节转换为int型,方便调用方法转二进制
int temp = str;
// 按位与
if (flag) {
temp |= 256;
}
// 转为二进制,返回的是二进制的补码
String s = Integer.toBinaryString(temp);
// 是正数就要补高位
if (flag) {
return s.substring(s.length() - 8);
} else { // 否则返回这个二进制字符串
return s;
}
}
/**
* 将经过处理后的赫夫曼编码数组转化为原始字符串的字符数组
*
* @param hashManCodes 哈夫曼编码表
* @param hashManArr 处理后的赫夫曼数组
* @return 原始字符串的字符数组
*/
public static byte[] decode(Map<Byte, String> hashManCodes, byte[] hashManArr) {
/* 1.将赫夫曼数组转化为二进制字符串,例如:[12,34,56,78,34,56] ---------> "000000010111111111111101" */
// 定义一个StringBuilder,用于拼接每个字符对应的二进制编码 例如:88---->000101000..
StringBuilder stringBuilder = new StringBuilder();
// 循环遍历赫夫曼数组,将每个字符转化为对应的二进制编码
for (int i = 0; i < hashManArr.length; i++) {
// 定义一个变量,用于接收遍历到的每个字符
byte b = hashManArr[i];
// 判断是否是最后一个字符
boolean flag = (i == hashManArr.length - 1);
// 得到每个字符的二进制编码
String s = byteToBinaryCodeString(!flag, b);
// 拼接处字符的二进制编码
stringBuilder.append(s);
}
System.out.println("赫夫曼数组对应的二进制字符串是:" + stringBuilder.toString());
/* 2.遍历(截取)得到的二进制字符串,通过查找赫夫曼表,将其转化为对应的字符 */
// 逆向查找赫夫曼表,所以定义一个新的HashMap,存储字符及其对应的二进制 例如:0100-87
HashMap<String, Byte> NewHashMap = new HashMap<>();
// 遍历原始的赫夫曼编码表
for (Map.Entry<Byte, String> entry : hashManCodes.entrySet()) {
NewHashMap.put(entry.getValue(), entry.getKey());
}
// System.out.println("新的赫夫曼编码表是:" + NewHashMap);
// 截取二进制编码,依次转化为对应的字符
// 创建一个集合,存放字节
List<Byte> ByteList = new ArrayList<>();
// 遍历stringBuilder
for (int i = 0; i < stringBuilder.length(); ) {
int count = 1; // 用于动态截取二进制编码
boolean flag = true; // 标志位
Byte key = null; // 变量提升
// 循环遍历二进制编码
while (flag) {
String str = stringBuilder.substring(i, i + count);
key = NewHashMap.get(str);
if (key == null) { // 匹配失败,继续找
count++;
} else { // 匹配成功,退出循环
flag = false;
}
}
// 将匹配到的字符添加到集合中
ByteList.add(key);
// 匹配下一个字节
i += count;
}
// for循环结束后,集合中存放了原始数据的字符串
// 3.要把这些字符串添加到一个字符数组中
byte[] byteArr = new byte[ByteList.size()];
for (int i = 0; i < ByteList.size(); i++) {
byteArr[i] = ByteList.get(i);
}
return byteArr;
}
}