package com.jiahui.huffmanTree;
import java.io.*;
import java.util.*;
/**
* @author shkstart
* @create 2022-11-17 18:52
*/
public class HuffmanTree {
public static void main(String args[]) throws IOException {
// String str="i like like like java do you like a java";
// Map<Byte, Integer> number = getNumber(str);
//
// Node node = huffmanTree(number);
// Map<Byte,String> huffmanCode=new HashMap<>();
// getCodes(node.getLeft(),"0",new StringBuilder(),huffmanCode);
// getCodes(node.getRight(),"1",new StringBuilder(),huffmanCode);
//
// for (Map.Entry<Byte, String> i:huffmanCode.entrySet()){
// System.out.println(i.getKey()+"--"+i.getValue());
// }
// byte[] zipHuffmanCode = zipHuffman(str, huffmanCode);
// System.out.println(Arrays.toString(zipHuffmanCode));
// byte b=1;
// byte[] bytes = deCode(zipHuffmanCode, huffmanCode);
// System.out.println(Arrays.toString(bytes));
// byte[] bytes={65,66};
// String s = new String(bytes);
// System.out.println(s);
File srcFile=new File("D:\\尚硅谷Java基础\\传入");
try {
srcFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
File dstFile=new File("D:\\尚硅谷Java基础\\输出");
dstFile.createNewFile();
FileOutputStream fos=new FileOutputStream(srcFile);
fos.write("i like like like java do you like a java".getBytes());
File file=new File("D:\\尚硅谷Java基础\\传入1");
file.createNewFile();
zipFile("D:\\尚硅谷Java基础\\传入","D:\\尚硅谷Java基础\\输出");
unzipFile("D:\\尚硅谷Java基础\\输出","D:\\尚硅谷Java基础\\传入1");
//失败案例,试图压缩图片,然后下标越界,日后再看吧
// File srcFile=new File("C:\\Users\\x'j'h'y'y'd's\\Desktop\\csdn\\ali.jpg");
// try {
// srcFile.createNewFile();
// } catch (IOException e) {
// e.printStackTrace();
// }
// File dstFile=new File("D:\\尚硅谷Java基础\\图片.jpg");
// dstFile.createNewFile();
// FileOutputStream fos=new FileOutputStream(srcFile);
// fos.write("i like like like java do you like a java".getBytes());
// File file=new File("D:\\尚硅谷Java基础\\图片1.jpg");
// file.createNewFile();
// zipFile("C:\\Users\\x'j'h'y'y'd's\\Desktop\\csdn\\ali.jpg","D:\\尚硅谷Java基础\\图片.jpg");
// unzipFile("D:\\尚硅谷Java基础\\图片.jpg","D:\\尚硅谷Java基础\\图片1.jpg");
}
public static void preOrder(Node o){
if (o!=null){
o.preOrder();
}else {
System.out.println("是空树,不能遍历");
}
}
//第一版本(赫夫曼树版)
// public static Node huffmanTree(int[] arry){
// List<Node> Nodes=new ArrayList<>();
//
// for (int i :arry){
// Node temp =new Node(i);
// Nodes.add(temp);
// }
//
// while (Nodes.size()>1){
// Collections.sort(Nodes);
// Node one =Nodes.get(0);
// Node two=Nodes.get(1);
//
// Node parent =new Node(one.getNumber()+two.getNumber());
// parent.setLeft(one);
// parent.setRight(two);
//
// Nodes.remove(one);
// Nodes.remove(two);//需要注意ArrayList类remove后,后一位的对象直接向前递进
// Nodes.add(parent);
//
// }
//
// return Nodes.get(0);
//
// }
//将文件解压
public static void unzipFile(String srcFile,String dstFile) {
ObjectInputStream ois= null;
FileOutputStream fos= null;
try {
ois = new ObjectInputStream(new FileInputStream(new File(srcFile)));
fos = new FileOutputStream(new File(dstFile));
byte[] bytes =(byte[]) ois.readObject();
Map<Byte,String> huffmanCode=(Map<Byte,String> ) ois.readObject();
byte[] bytes1 = unzipCode(bytes, huffmanCode);
fos.write(bytes1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (fos!=null){
fos.close();
}
if (ois!=null){
ois.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//将文件压缩
public static void zipFile(String srcFile,String dstFile) {
FileInputStream fis= null;
ObjectOutputStream oos= null;
try {
fis = new FileInputStream(new File(srcFile));
oos = new ObjectOutputStream(new FileOutputStream(new File(dstFile)));
byte[] bytes=new byte[fis.available()];
fis.read(bytes);
Map<Byte, String> huffmanCode = getHuffmanCode(new String(bytes));
byte[] bytes1 = zipCode(new String(bytes), huffmanCode);
oos.writeObject(bytes1);
oos.flush();
oos.writeObject(huffmanCode);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (oos!=null){
oos.close();
}
if (fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//解压内容
public static byte[] unzipCode(byte[] bytes,Map<Byte,String> huffmanCode){
byte[] bytes1 = deCode(bytes, huffmanCode);
return bytes1;
}
//运用赫夫曼编码表压缩内容
public static byte[] zipCode(String str,Map<Byte,String> huffmanCode){
byte[] bytes = zipHuffman(str, huffmanCode); //压缩后得到的数组,一个byte代表一个字符
return bytes;
}
//得到赫夫曼编码表
public static Map<Byte,String> getHuffmanCode(String str){
Map<Byte, Integer> number = getNumber(str);//得到每个字符出现的次数
Node node = huffmanTree(number);//得到每个字符的huffman树
Map<Byte,String> huffmanCode=new HashMap<>();//赫夫曼编码表
getCodes(node.getLeft(),"0",new StringBuilder(),huffmanCode);//往左边走 将字符串转成huffman编码
getCodes(node.getRight(),"1",new StringBuilder(),huffmanCode);//往右边走
return huffmanCode;
}
//将得到的编码通过赫夫曼编码表转换成最初的string并将他们拼接起来返回,解码完成
public static byte[] deCode(byte[] zipHuffmanCode ,Map<Byte,String> huffmanCode){
StringBuilder stringBuilder=new StringBuilder();
for (int i=0;i<zipHuffmanCode.length;i++){
boolean isFlag=false;
if (i==zipHuffmanCode.length-1){
isFlag=true;
}
stringBuilder.append(byteToHuffmanString(zipHuffmanCode[i],isFlag));
}
Map<String ,Byte> map=new HashMap<>();
for (Map.Entry<Byte,String> i: huffmanCode.entrySet()){
map.put(i.getValue(),i.getKey());
}
String str=stringBuilder.toString();
List<Byte> list=new ArrayList<>();
for (int i=0;i<str.length();){
String temp;
int count=i+1;//长度
while (true){
temp=str.substring(i,count);
if (map.containsKey(temp)){
break;
}
count++;
}
list.add(map.get(temp));
i=count;
}
byte[] bytes=new byte[list.size()];
for (int i=0;i<bytes.length;i++){
bytes[i]=list.get(i);
}
return bytes;
}
//将byte转换为二进制字符串,也就是最开始的霍夫曼编码得到的编码
//isFlag为true表示最后一位为正
//需要考虑最后一个byte,如果是正数,不需要补码,直接加,如果是负数,说明当初转换的时候满了8位,没影响
//但是最后一个byte是正数没有满8位
public static String byteToHuffmanString(byte b,boolean isFlag){
int temp=b;
String s = Integer.toBinaryString(temp);
// 正数补完高位后,第九位为1,后面取后八位,不影响,而且八位全部取到,没有少
if (isFlag){//最后一位为正,返回的不足8位
return s;
}
temp|=256;
s=Integer.toBinaryString(temp);
s=s.substring(s.length()-8);//取后八位,得到的都是补码,但是当初编码的时候(byte)Interger.paeInt(string)
// 就是把字符串当成二进制的补码,因此不影响,得到的s就是我们需要的编码
return s;
}
//将字符串转成HuffmanCode编码的字节,也就是将正常字符串通过Huffmancode进行压缩
public static byte[] zipHuffman(String str,Map<Byte,String> huffmanCode){
byte[] contentByte=str.getBytes();
StringBuilder stringBuilder=new StringBuilder();
for (byte i:contentByte){
stringBuilder.append(huffmanCode.get(i));
}
int len =0;
if (stringBuilder.length()%8==0){
len=stringBuilder.length()/8;
}else {
len=stringBuilder.length()/8+1;
}
byte[] zipHuffmanCode=new byte[len];
// System.out.println(stringBuilder.length()); 测试,看看压缩后的字符串长度是否符合
// System.out.println(stringBuilder);
int index =0;
for (int i=0;i<stringBuilder.length();i+=8){
String temp="";
if (i+8>stringBuilder.length()){
temp=stringBuilder.substring(i);
}else {
temp=stringBuilder.substring(i,i+8);
}
byte b=(byte) Integer.parseInt(temp,2);//该方法将传入的temp字符串代表的二进制当成补码使用
zipHuffmanCode[index]=b;
index++;
}
return zipHuffmanCode;
}
//得到每个字符的出现次数
public static Map<Byte,Integer> getNumber(String str){
Map<Byte,Integer> numberMap=new HashMap<>() ;
byte[] array=str.getBytes();
for (byte i:array){
if (!numberMap.containsKey(i)){
numberMap.put( i,1);
}else {
numberMap.put(i,numberMap.get(i)+1);
}
}
return numberMap;
}
// 第二版本赫夫曼树(赫夫曼编码版)
public static Node huffmanTree(Map<Byte,Integer> numberMap){
Set<Map.Entry<Byte, Integer>> entries = numberMap.entrySet();
List<Node> nodes=new ArrayList<>();
for (Map.Entry<Byte,Integer> entry:entries){
Byte ch = entry.getKey();
int i=entry.getValue();
Node node=new Node(i,ch);
nodes.add(node);
}
while (nodes.size()>1){
Collections.sort(nodes);
Node one =nodes.get(0);
Node two=nodes.get(1);
Node parent =new Node(one.getNumber()+two.getNumber());
parent.setLeft(one);
parent.setRight(two);
nodes.remove(one);
nodes.remove(two);//需要注意ArrayList类remove后,后一位的对象直接向前递进
nodes.add(parent);
}
return nodes.get(0);
}
//每个字符对应的赫夫曼编码
/**
*
* @param node 字符对应的huffman树
* @param code 当前走的路径,左还是右
* @param stringBuilder 走过的路径
* @param huffmanCode 最后得到的每个字符转换成的赫夫曼编码对应的集合,byte代表字符,string代表赫夫曼编码
*/
public static void getCodes(Node node,String code,StringBuilder stringBuilder,Map<Byte,String> huffmanCode){
StringBuilder stringBuilder1=new StringBuilder(stringBuilder);
stringBuilder1.append(code);
if (node!=null){
if (node.getData()!=null){
huffmanCode.put(node.getData(),stringBuilder1.toString());
}else {
getCodes(node.getLeft(),"0",stringBuilder1,huffmanCode);
getCodes(node.getRight(),"1",stringBuilder1,huffmanCode);
}
}
}
}
class Node implements Comparable<Node>{
private int number;
private Byte data;
private Node left;
private Node right;
public Node(int number, byte data){
this.number=number;
this.data=data;
}
public Byte getData() {
return data;
}
public void setData(Byte data) {
this.data = data;
}
public void preOrder(){
System.out.println(this.getNumber());
if (this.left!=null){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
}
public Node(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"number=" + number +
", data=" + data +
'}';
}
@Override
public int compareTo(Node o) {
return this.number-o.number;
}
}
这是个大工程,严格算时间感觉写了一个多礼拜,每天能抽出来写的时间并不多,还是太菜了。
遇到的一些相关问题都标在注释里面了,希望对大家有些帮助。
1.byte b=(byte) Integer.parseInt(temp,2); 该方法将传入的temp字符串代表的二进制当成补码使用,因为计算机底层都是补码形式。
2.int temp=b;
String s = Integer.toBinaryString(temp);
该方法转变的负数得到的位数远远大于8位,但是后八位的形式是数字temp的二进制的补码形式。
因此与256做一次或运算或者不做也可以,直接subString取后八位即可。
该方法得到的正数,由于正数第8位(即符号位)为0,所以会直接取到最后一个1的位置,剩下的省略,因此得到的不足8位,此时如果想得到8位,与256做一次或运算然后取后8位,舍弃第九位即可。