思路:
- 把字符串转化为字节数组,对字符串中不同字符出现的次数进行统计,统计结果存储到一个Map中
- 键为字符字节,值为此字符字节在字符串中出现的次数
- 创建赫夫曼树
- 读取每一个字符字节的路径方向值,把字符串中的每个字符用路径方向值来表示
- 把得到的二进制字符串切分为有符号字节存储到字节数组中
- 打印原字符串,二进制字符串,字节数组
代码如下:
//Node类用于创建树结构
public class Node implements Comparable<Node>{
Byte data;
int weight;
Node left;
Node right;
public Node(Byte data, int weight){
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
@Override
public int compareTo(Node nodes) {
// TODO Auto-generated method stub
return nodes.weight - this.weight;
}
}
public class TestHuffmanCode {
public static void main(String[] args) {
//定义一个字符串
String msg ="can you can a can as a can cannr can a can";
//定义一个字节数组
byte[] bytes = msg.getBytes();
//进行赫夫曼编码
byte[] byt = huffmanZip(bytes);
//循环遍历字节数组中的值,也即赫夫曼编码值
for(byte b:byt) {
System.out.print(b+" ");
}
}
/**
*进行赫夫曼编码的方法
*
*
**/
private static byte[] huffmanZip(byte[] bytes){
//判断字节是否为空
if(bytes ==null) {
return null;
}
//先统计每一个byte出现的次数,并放入一个集合中
List<Node> nodes = getNodes(bytes);
//创建一个赫夫曼树
Node tree = createHuffmanTree(nodes);
//创建一个赫夫曼编码表
Map<Byte, String>huffCodes = getCodes(tree);
//压缩编码
byte[] byt=zip(bytes,huffCodes);
return byt;
}
//用于标识最后一个子字符串的长度
static int bitFlag =0;
//把二进制字符串转换为有符号十进制数存储到字节数组中
private static byte[] zip(byte[] bytes, Map<Byte,String>huffCodes){
//用于存储二进制字符串
StringBuilder sb = new StringBuilder();
//把需要压缩的byte数组处理成一个二进制的字符串
for(byte b:bytes){
sb.append(huffCodes.get(b));
}
//输出二进制字符串
System.out.println(sb.toString());
//定义长度用来存储压缩后的字符串
int len;
//判断二进制字符串能划分为几个字节
if(sb.length()%8==0){
len = sb.length()/8;
}else{
len = sb.length()/8 + 1;
}
//用于记录byte数组中的下标值
int index = 0;
//用于存储压缩后的byte
byte[] by = new byte[len];
//循环遍历二进制字符串
for(int i =0;i<sb.length();i+=8){
//用于存储子字符串
String strByte;
if(i+8>sb.length()){
//赋值最后一个字符串
strByte = sb.substring(i);
//用于记录最后一个二进制字符串的长度
bitFlag = strByte.length();
//把二进制字符串转换为有符号十进制数
by[index] = (byte) Integer.parseInt(strByte,2);
}else{
//按照每八位截取二进制字符串
strByte = sb.substring(i,i+8);
//把二进制字符串转换为有符号十进制数
by[index] = (byte) Integer.parseInt(strByte,2);
index++;
}
}
return by;
}
//用来存储符号的路径方向
static StringBuilder sb = new StringBuilder();
//用于存储符号字节值与对应的符号路径方向值
static Map<Byte,String>huffCodes = new HashMap<>();
//用于得到符号字节值与对应的符号路径方向值
private static Map<Byte, String>getCodes(Node tree){
//判断赫夫曼树是否为空
if(tree==null){
return null;
}
//调用得到路径方向值方法
getCodes(tree.left,"0",sb);
getCodes(tree.right,"1",sb);
return huffCodes;
}
//得到路径值
private static void getCodes(Node node, String code,StringBuilder sb){
//定义一个StringBuilder,把参数传进去
StringBuilder sb2 = new StringBuilder(sb);
//追加路径值
sb2.append(code);
//递归getCodes方法
if(node.data == null){
getCodes(node.left,"0",sb2);
getCodes(node.right,"1",sb2);
}else{
huffCodes.put(node.data,sb2.toString());
}
}
private static Node createHuffmanTree(List<Node>nodes){
while(nodes.size()>1){
//排序
Collections.sort(nodes);
//创建左子树
Node left = nodes.get(nodes.size()-1);
//创建右子树
Node right = nodes.get(nodes.size()-2);
//创建双亲节点
Node parent = new Node(null, left.weight+right.weight);
//把左子树赋值给双亲节点的左子树
parent.left = left;
//把右子树赋值给双亲节点的右子树
parent.right = right;
//去除左右子树
nodes.remove(left);
nodes.remove(right);
//把双亲节点加入Node中
nodes.add(parent);
}
return nodes.get(0);
}
private static List<Node> getNodes(byte[] bytes){
//声明一个Node类型的动态数组
List<Node> nodes = new ArrayList<>();
//定义一个Map,用于存储一个字符值和此字符出现的次数
Map<Byte,Integer> counts = new HashMap<>();
//循环遍历字节数组
for(byte b:bytes){
//得到Map中的value值,做一个标记或者记录
Integer count = counts.get(b);
if(count==null){
//把子节数组中的数据存到Map中
counts.put(b,1);
}else{
//依次把子节数组中的数据存入Map中
counts.put(b,count+1);
}
}
//把键值对存入到Node类型的Node中
for(Map.Entry<Byte,Integer>entry:counts.entrySet()){
nodes.add(new Node(entry.getKey(),entry.getValue()));
}
//System.out.println(nodes);
return nodes;
}
}