概览
本文原载于我的博客,地址:https://blog.guoziyang.top/archives/24/
哈夫曼树也称为最优二叉树,是加权路径长度最短的二叉树。权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。每一个父节点的权值等于子节点的权值之和。
可以这么说,哈夫曼树中,真正有效的、存储着数据的,只有那些叶节点,而其它节点仅仅是为了构造树结构、以及保持总权值最小而存在的。
那么问题是,哈夫曼树和文本压缩有什么关系呢?这就不得不提到文本编码问题。
编码与哈夫曼树
编码(主要是二进制码),是指将一组对象(如字符集)中的每个对象用唯一的一个二进制位串表示。编码的方式有很多种,主要分为两类:等长的和不等长的。等长的自然好理解,每一个字符的二进制码长度都相等,那么不等长的呢?
很容易就可以想到,不等长的编码有一个问题:二义性,即一串编码可以有多种不同的方式解码,如以下这个例子:
字符E编码为00,字符T编码为01,字符W编码为0001
二进制串0001的解码则存在两种方式:ET和W
所以,我们需要这种编码保持前缀性,即一组编码中任意一个编码都不是其它任何一个编码的前缀。而哈夫曼树就可以为我们解决这个问题,即:从根节点到叶节点到路径不存在任何前缀性重复。
所以,我们引入了哈夫曼编码问题:对于给定的字符集及其每个字符出现的概率(使用频度),求该字符集的最优的前缀性编码。那么总体的思想就很明确了:
- 计算每个字符在文本中的出现频率
- 构造频率对应的哈夫曼树
- 利用这棵树对文本进行编码存储
编码
计算字符出现的频率
这段算法思想比较简单粗暴,直接遍历一遍,把字符和频次存在一个Map里,遍历到一个字符就把那个字符对应的频次加1,最后根据频次计算出该字符对应的频率即可,返回的也是个<字符,频率>的Map。
Java实现如下:
private static HashMap<Character, Double> calculateCharChance(String fileContent) {
if(fileContent == null) {
return null;
}
HashMap<Character, Double> charTimes = new HashMap<>();
for(int i = 0; i < fileContent.length(); i ++) {
char tempChar = fileContent.charAt(i);
if(charTimes.containsKey(tempChar)) {
charTimes.put(tempChar, charTimes.get(tempChar) + 1d);
} else {
charTimes.put(tempChar, 1d);
}
}
HashMap<Character, Double> perChance = new HashMap<>();
for(Map.Entry<Character, Double> entry : charTimes.entrySet()) {
perChance.put(entry.getKey(), entry.getValue() / fileContent.length());
}
return perChance;
}
构造哈夫曼树
接着我们就需要构造一棵哈夫曼树,我们首先定义一下每个节点的类:
class TreeNode implements Serializable{
private static final long serialVersionUID = 1L;
private Character currentChar = null;
private Double chance;
private TreeNode parentNode;
private TreeNode leftNode;
private TreeNode rightNode;
public TreeNode(char currentChar, double chance) {
this.currentChar = currentChar;
this.chance = chance;
}
public TreeNode(double chance) {
this.chance = chance;
}
public Character getCurrentChar() {
return currentChar;
}
public Double getChance() {
return chance;
}
public void setParentNode(TreeNode parentNode) {
this.parentNode = parentNode;
}
public TreeNode getParentNode() {
return parentNode;
}
public void setLeftNode(TreeNode leftNode) {
this.leftNode = leftNode;
}
public TreeNode getLeftNode() {
return leftNode;
}
public void setRightNode(TreeNode rightNode) {
this.rightNode = rightNode;
}
public TreeNode getRightNode() {
return rightNode;
}
}
其中,currentChar字段表示该节点代表的字符,当然,只有叶节点才会有这个字段,chance表示该节点的权重(叶节点的字符的概率)。
哈夫曼树的构造算法如下:
1. 由给定的权值构造n棵只有一个根节点、左右子树均为空的二叉树,得到一个二叉树集合
2. 当集合中的元素大于1时,循环执行:
2.1. 选取集合中根节点权值最小的两棵树作为左右子树,权值之和作为父节点的权值构造二叉树
2.2. 将这棵树添加进集合,并且删去原来的两棵树
3. 集合中留下的最后一棵树即为哈夫曼树
代码比较简单:
private static TreeNode buildHuffmanTree(HashMap<Character, Double> perChance) {
if(perChance == null) {
return null;
}
ArrayList<TreeNode> treeNodes = new ArrayList<>();
leafNodes = new HashMap<>();
for(Map.Entry<Character, Double> entry : perChance.entrySet()) {
TreeNode tempNode = new TreeNode(entry.getKey(), entry.getValue());
treeNodes.add(tempNode);
leafNodes.put(tempNode.getCurrentChar(), tempNode);
}
TreeNodeComparator comparator = new TreeNodeComparator();
while(treeNodes.size() != 1) {
Collections.sort(treeNodes, comparator);
TreeNode tempNode1 = treeNodes.remove(0);
TreeNode tempNode2 = treeNodes.remove(0);
TreeNode tempTop = new TreeNode(tempNode1.getChance() + tempNode2.getChance());
tempTop.setLeftNode(tempNode1);
tempTop.setRightNode(tempNode2);
tempNode1.setParentNode(tempTop);
tempNode2.setParentNode(tempTop);
treeNodes.add(tempTop);
}
return treeNodes.get(0);
}
其中,leafNodes是一个Map,Key为字符,Value是该字符对应的叶节点,便于直接获取哈夫曼树的叶节点而不需要遍历搜索。
对文本编码
对文本编码分为两个步骤:获得每个字符对应的编码,对文本进行编码
获得字符编码
通过leafNodes,我们可以直接获取到文本中出现过的每一个字符,以及对应的叶节点。从叶节点开始,一层一层向上寻找,直到根节点为止,若该节点为父节点的左节点,则编码字符串后面添一个1,否则添0。最后我们可以得到这个叶节点到根节点的一个01序列。注意,这个序列是叶节点到根节点的路径,而哈夫曼编码是根节点到叶节点的路径,所以得到的字符串还应该倒置一下才是哈夫曼编码。
HashMap<Character, String> codeMap = new HashMap<>();
for(Map.Entry<Character, TreeNode> entry : leafNodes.entrySet()) { TreeNode tempNode = entry.getValue();
TreeNode tempHeadNode = null;
String code = "";
while(tempNode.getParentNode() != null) {
tempHeadNode = tempNode.getParentNode();
if(tempNode.equals(tempHeadNode.getLeftNode())) {
code += "0";
} else {
code += "1";
}
tempNode = tempNode.getParentNode();
}
codeMap.put(entry.getValue().getCurrentChar(), new StringBuilder(code).reverse().toString());
}
对文本编码
对文本编码就比较容易了,遍历原文本,在codeMap中找到对应字符的01序列即可。
StringBuilder encraptedContent = new StringBuilder();
for(int i = 0; i < fileContent.length(); i ++) {
encraptedContent.append(codeMap.get(fileContent.charAt(i)));
}
binaryNumber = encraptedContent.length();
encraptedContent = encraptedContent.insert(0, toFullBinaryString(binaryNumber));
int moreZero = 8 - encraptedContent.length() % 8;
for(int i = 0; i < moreZero; i ++) {
encraptedContent.append("0");
}
return encraptedContent.toString();
写入文件
本来以为是最简单的一部分,没想到确实最让人头疼的一部分。
也许你已经注意到了,在对文本编码的部分,我将最后的编完码的字符串用0补位,补成8的倍数,就是因为写入的时候,是一个字节一个字节写入的,一个字节是8位。
而且最重要的问题是,我们不可以使用字符流!试想,如果我们使用字符流,会怎么样呢?
譬如一个a,在文件中的存储形式是ascii码,也就是0110 0001,一个字节。编码之后,假设是00101,如果按照字符流写入的话,00101还会被转化为ascii码写入,占空间高达5个字节!越压缩越大了。
所以,我们压缩之后的文件应当是按照字节写入的,同时,每个01应当是一个二进制位。
Java中有一个基本数据类型byte,对应着存储结构中的一个字节,我们需要改变byte的每一个位,按位或即可。
譬如,我们需要将一个byte的第1位改为1,我们只需要将这个byte强转为int之后,和0x80(10000000)相与,最后再转为byte即可。
最后还会遇到一个问题,当我们对01序列凑整的时候(凑整为8的倍数),我们使用的是补0操作,将序列补为8的倍数。可是如果解码的时候,当正常的文本内容读取完成时,会对那些0进行解码,如果恰巧可以解码出字符的话,文本后面就会出现一些奇怪的字符。
我们处理方式你们可能也注意到了,在对文本编码的时候,我记录了有效的01序列的个数,并将其转化为32位的二进制数,对文本编码时调用的toFullBinaryString()就是将int转化为32位二进制数的。编码时将这个二进制数存在这个密码文件的开头4个字节处,理论上这种方法可以记录有效的2^32 = 4294967296位,也就是42亿多,应该是足够了!
当然,我们解码的时候也需要这棵哈夫曼树帮助解码,所以我们也需要保存这棵哈夫曼树,这里我只是简单地将这棵树用ObjectOutputStream序列化到一个key文件中了。
代码如下:
private static void writeIntoFile(String encraptedContent) {
if(encraptedContent == null) {
return;
}
char num1 = "1".charAt(0);
int byteLength = encraptedContent.length() / 8;
byte[] bytes = new byte[byteLength];
for(int i = 0; i < byteLength; i ++) {
bytes[i] = (byte)0;
}
for(int i = 0; i < encraptedContent.length(); i ++) {
char tempBinary = encraptedContent.charAt(i);
if(tempBinary == num1) {
switch(i % 8) {
case 0: bytes[i/8] = (byte)((int)bytes[i/8] | 0x80);break;
case 1: bytes[i/8] = (byte)((int)bytes[i/8] | 0x40);break;
case 2: bytes[i/8] = (byte)((int)bytes[i/8] | 0x20);break;
case 3: bytes[i/8] = (byte)((int)bytes[i/8] | 0x10);break;
case 4: bytes[i/8] = (byte)((int)bytes[i/8] | 0x8);break;
case 5: bytes[i/8] = (byte)((int)bytes[i/8] | 0x4);break;
case 6: bytes[i/8] = (byte)((int)bytes[i/8] | 0x2);break;
case 7: bytes[i/8] = (byte)((int)bytes[i/8] | 0x1);break;
}
}
}
File encrapyFile = new File(file.getName().split("\\.")[0] + ".encrapy");
File keyFile = new File(file.getName().split("\\.")[0] + ".key");
try{
FileOutputStream outputStream = new FileOutputStream(encrapyFile);
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(keyFile));
objectOutputStream.writeObject(topNode);
objectOutputStream.close();
}catch(Exception e) {
System.out.println("加密后文件写入失败!");
}
System.out.println("\n密码文件为 " + encrapyFile.getName() + "\nKey文件为 " + keyFile.getName());
System.out.println("压缩前文件大小 " + file.length() + " Byte\n压缩后文件大小 " + encrapyFile.length() + " Byte");
System.out.printf("压缩率:%.2f%%\n", (((float)file.length() - (float)encrapyFile.length()) / (float)file.length()) * 100);
}
效果
GuodeMacBook-Air:HuffmanHomework guoziyang$ javaHuffmanHomework
1. 原始文件压缩
2. 压缩文件解码
3. 退出
请输入选择:1
请输入加密的文件路径:example4.txt
密码文件为 example4.encrapy
Key文件为 example4.key
压缩前文件大小 1015585 Byte
压缩后文件大小 564063 Byte
压缩率:44.46%
解码
解码的方式也是比较简单的,读入密码文件,转化为二进制字符串,读入key文件,转化为哈夫曼树,根据序列查找字符就行了。
读入文件
读入key文件很简单,也就是用ObjectInputStream读进来就好了。
主要是读入密码文件以及处理。
读入的时候我们当然不可以用字符流,否则将会把01序列认作ascii码序列转化为字符,我们需要的是原始的01序列。所以我们选择字节流。
同时我们还需要一个类,ByteArrayOutputStream,这个类内部有一个缓冲区,可以使用toByteArray()方法将缓冲区内部的所有byte转化为一个byte数组。
InputStream inputStream = new FileInputStream(encrapyFileName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while((n = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, n);
}
bytes = byteArrayOutputStream.toByteArray();
循环读到的字节直接写到ByteArrayOutputStream里,最后toByteArray()即可。
现在我们拿到了代表密码内容的字节数组,转化为二进制String的方法如下:
StringBuilder builder = new StringBuilder();
for(byte tempByte : bytes) {
builder.append(Integer.toBinaryString((tempByte & 0xFF) + 0x100).substring(1));
}
return builder.toString();
我主要解释下Integer.toBinaryString((tempByte & 0xFF) + 0x100).substring(1))的作用。
这条语句将byte转化为对应的二进制串。当一个byte类型直接转化为int时,JVM会对它进行补位操作,将8位补为32位。譬如,byte存储的是10000001,转为int之后1111111111111111111111111 10000001(32位),很显然这两个补码保存的十进制数字依旧是相同的。可是问题在于,我们做byte -> int的转化,并不是为了保持十进制的一致性,而是为了拿到其二进制补码,所以我们使用0xFF与这个byte相与,保证前24位依旧是0。将这个码加上0x100(100000000)将第9位置为1,这样即使前几位为0的话也可以不被舍掉,最后使用substring(1)将第九位上的1去掉即可。
按照哈夫曼树解码
我们拿到了原始的二进制字串和哈夫曼树,只需要解码就完事了。
解码算法如下:
1. 将指针置于哈夫曼树的头节点
2. 依次读入二进制码
2.1 如果读入0,则指针指向当前节点的左支,否则指向右支
2.2 判断当前节点是否是叶节点,如果是则将该节点的字符输出并将指针置于头节点
2.3 继续循环
由此即可输出原文本内容,注意我们在编码的时候把一个四字节32位的二进制数存在文件头,应当首先读出,以确认有效的二进制码位数。
代码如下:
StringBuilder resultBuilder = new StringBuilder();
binaryNumber = binaryToTen(encraptedContent.substring(0, 32));
encraptedContent = encraptedContent.substring(32);
TreeNode tempNode = topNode;
for(int i = 0; i < binaryNumber; i ++) {
if(encraptedContent.charAt(i) == '0') {
tempNode = tempNode.getLeftNode();
} else {
tempNode = tempNode.getRightNode();
}
if(tempNode.getCurrentChar() != null) {
resultBuilder.append(tempNode.getCurrentChar());
tempNode = topNode;
}
}
return resultBuilder.toString()
完整代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Collections;
import java.io.File;
import java.io.FileInputStream;
public class HuffmanHomework {
private static Scanner scanner = new Scanner(System.in);
private static HashMap<Character, TreeNode> leafNodes = null;
private static TreeNode topNode = null;
private static File file = null;
private static int binaryNumber = 0;
public static void main(String[] args) {
while(true) {
menu();
}
}
private static void menu() {
System.out.println();
System.out.println();
System.out.println("1. 原始文件压缩");
System.out.println("2. 压缩文件解码");
System.out.println("3. 退出");
System.out.print("请输入选择:");
int choice = scanner.nextInt();
if(choice != 1 && choice != 2 && choice != 3) {
System.out.println("输入有误,请重新选择!");
System.out.println();
System.out.println();
return;
}
System.out.println();
System.out.println();
switch(choice) {
case 1:
encrapt();
break;
case 2:
decrapt();
break;
case 3:
System.exit(0);
}
}
private static void encrapt() {
String fileContent = readFromFile();
HashMap<Character, Double> perChance = calculateCharChance(fileContent);
topNode = buildHuffmanTree(perChance);
String encraptedContent = encraptContent(fileContent);
writeIntoFile(encraptedContent);
}
private static String readFromFile() {
System.out.print("请输入加密的文件路径:");
scanner = new Scanner(System.in);
String fileName = scanner.nextLine();
file = new File(fileName);
try{
BufferedReader reader = new BufferedReader(new FileReader(file));
StringBuilder fileContentBuilder = new StringBuilder();
String tempString = null;
while((tempString = reader.readLine()) != null) {
fileContentBuilder.append(tempString);
fileContentBuilder.append("\n");
}
reader.close();
return fileContentBuilder.toString();
}catch(IOException exception) {
System.out.println("读入文件失败!请检查文件路径!");
return null;
}
}
private static HashMap<Character, Double> calculateCharChance(String fileContent) {
if(fileContent == null) {
return null;
}
HashMap<Character, Double> charTimes = new HashMap<>();
for(int i = 0; i < fileContent.length(); i ++) {
char tempChar = fileContent.charAt(i);
if(charTimes.containsKey(tempChar)) {
charTimes.put(tempChar, charTimes.get(tempChar) + 1d);
} else {
charTimes.put(tempChar, 1d);
}
}
HashMap<Character, Double> perChance = new HashMap<>();
for(Map.Entry<Character, Double> entry : charTimes.entrySet()) {
perChance.put(entry.getKey(), entry.getValue() / fileContent.length());
}
return perChance;
}
private static TreeNode buildHuffmanTree(HashMap<Character, Double> perChance) {
if(perChance == null) {
return null;
}
ArrayList<TreeNode> treeNodes = new ArrayList<>();
leafNodes = new HashMap<>();
for(Map.Entry<Character, Double> entry : perChance.entrySet()) {
TreeNode tempNode = new TreeNode(entry.getKey(), entry.getValue());
treeNodes.add(tempNode);
leafNodes.put(tempNode.getCurrentChar(), tempNode);
}
TreeNodeComparator comparator = new TreeNodeComparator();
while(treeNodes.size() != 1) {
Collections.sort(treeNodes, comparator);
TreeNode tempNode1 = treeNodes.remove(0);
TreeNode tempNode2 = treeNodes.remove(0);
TreeNode tempTop = new TreeNode(tempNode1.getChance() + tempNode2.getChance());
tempTop.setLeftNode(tempNode1);
tempTop.setRightNode(tempNode2);
tempNode1.setParentNode(tempTop);
tempNode2.setParentNode(tempTop);
treeNodes.add(tempTop);
}
return treeNodes.get(0);
}
private static String encraptContent(String fileContent) {
if(fileContent == null) {
return null;
}
HashMap<Character, String> codeMap = new HashMap<>();
for(Map.Entry<Character, TreeNode> entry : leafNodes.entrySet()) {
TreeNode tempNode = entry.getValue();
TreeNode tempHeadNode = null;
String code = "";
while(tempNode.getParentNode() != null) {
tempHeadNode = tempNode.getParentNode();
if(tempNode.equals(tempHeadNode.getLeftNode())) {
code += "0";
} else {
code += "1";
}
tempNode = tempNode.getParentNode();
}
codeMap.put(entry.getValue().getCurrentChar(), new StringBuilder(code).reverse().toString());
}
StringBuilder encraptedContent = new StringBuilder();
for(int i = 0; i < fileContent.length(); i ++) {
encraptedContent.append(codeMap.get(fileContent.charAt(i)));
}
binaryNumber = encraptedContent.length();
encraptedContent = encraptedContent.insert(0, toFullBinaryString(binaryNumber));
int moreZero = 8 - encraptedContent.length() % 8;
for(int i = 0; i < moreZero; i ++) {
encraptedContent.append("0");
}
return encraptedContent.toString();
}
public static String toFullBinaryString(int num) {
char[] chs = new char[Integer.SIZE];
for (int i = 0; i < Integer.SIZE; i++) {
chs[Integer.SIZE - 1 - i] = (char) (((num >> i) & 1) + '0');
}
return new String(chs);
}
private static void writeIntoFile(String encraptedContent) {
if(encraptedContent == null) {
return;
}
char num1 = "1".charAt(0);
int byteLength = encraptedContent.length() / 8;
byte[] bytes = new byte[byteLength];
for(int i = 0; i < byteLength; i ++) {
bytes[i] = (byte)0;
}
for(int i = 0; i < encraptedContent.length(); i ++) {
char tempBinary = encraptedContent.charAt(i);
if(tempBinary == num1) {
switch(i % 8) {
case 0: bytes[i/8] = (byte)((int)bytes[i/8] | 0x80);break;
case 1: bytes[i/8] = (byte)((int)bytes[i/8] | 0x40);break;
case 2: bytes[i/8] = (byte)((int)bytes[i/8] | 0x20);break;
case 3: bytes[i/8] = (byte)((int)bytes[i/8] | 0x10);break;
case 4: bytes[i/8] = (byte)((int)bytes[i/8] | 0x8);break;
case 5: bytes[i/8] = (byte)((int)bytes[i/8] | 0x4);break;
case 6: bytes[i/8] = (byte)((int)bytes[i/8] | 0x2);break;
case 7: bytes[i/8] = (byte)((int)bytes[i/8] | 0x1);break;
}
}
}
File encrapyFile = new File(file.getName().split("\\.")[0] + ".encrapy");
File keyFile = new File(file.getName().split("\\.")[0] + ".key");
try{
FileOutputStream outputStream = new FileOutputStream(encrapyFile);
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(keyFile));
objectOutputStream.writeObject(topNode);
objectOutputStream.close();
}catch(Exception e) {
System.out.println("加密后文件写入失败!");
}
System.out.println("\n密码文件为 " + encrapyFile.getName() + "\nKey文件为 " + keyFile.getName());
System.out.println("压缩前文件大小 " + file.length() + " Byte\n压缩后文件大小 " + encrapyFile.length() + " Byte");
System.out.printf("压缩率:%.2f%%\n", (((float)file.length() - (float)encrapyFile.length()) / (float)file.length()) * 100);
}
private static void decrapt() {
String encraptedContent = readEncraptedContent();
readKeyFile();
decraptContent(encraptedContent);
}
private static String readEncraptedContent() {
scanner = new Scanner(System.in);
System.out.print("请输入密码文件路径:");
String encrapyFileName = scanner.nextLine();
byte[] bytes = null;
try{
InputStream inputStream = new FileInputStream(encrapyFileName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n = 0;
while((n = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, n);
}
bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
inputStream.close();
}catch(IOException e) {
System.out.println("读入密码文件失败!请检查文件路径!");
return null;
}
StringBuilder builder = new StringBuilder();
for(byte tempByte : bytes) {
builder.append(Integer.toBinaryString((tempByte & 0xFF) + 0x100).substring(1));
}
return builder.toString();
}
private static void readKeyFile() {
scanner = new Scanner(System.in);
System.out.print("请输入Key文件路径:");
String keyFileName = scanner.nextLine();
try{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(keyFileName));
topNode = (TreeNode)objectInputStream.readObject();
objectInputStream.close();
}catch(Exception e) {
System.out.println("读入Key文件失败!请检查文件路径!");
topNode = null;
return;
}
}
private static void decraptContent(String encraptedContent) {
if(encraptedContent == null) {
return;
}
StringBuilder resultBuilder = new StringBuilder();
binaryNumber = binaryToTen(encraptedContent.substring(0, 32));
encraptedContent = encraptedContent.substring(32);
TreeNode tempNode = topNode;
for(int i = 0; i < binaryNumber; i ++) {
if(encraptedContent.charAt(i) == '0') {
tempNode = tempNode.getLeftNode();
} else {
tempNode = tempNode.getRightNode();
}
if(tempNode.getCurrentChar() != null) {
resultBuilder.append(tempNode.getCurrentChar());
tempNode = topNode;
}
}
System.out.print("解密完成,是否输出(y or n)?");
String choice = scanner.next();
if("y".equals(choice)) {
System.out.println("\n\n" + resultBuilder.toString());
}
System.out.print("是否写入文件(y or n)?");
choice = scanner.next();
if("y".equals(choice)) {
System.out.print("请输入待写入文件路径:");
scanner = new Scanner(System.in);
String resultFileName = scanner.nextLine();
try{
BufferedWriter writer = new BufferedWriter(new FileWriter(resultFileName));
writer.write(resultBuilder.toString());
writer.close();
}catch(Exception e) {
System.out.println("写入结果文件失败!请检查文件路径!");
}
}
}
private static int binaryToTen(String binary) {
int res = 0;
for(int i = 0; i < 32; i ++) {
if(binary.charAt(i) == '1') {
res += Math.pow(2, 31 - i);
}
}
return res;
}
}
class TreeNodeComparator implements Comparator<TreeNode> {
@Override
public int compare(TreeNode node1, TreeNode node2) {
return node1.getChance() > node2.getChance() ? 1 : -1;
}
}
class TreeNode implements Serializable{
private static final long serialVersionUID = 1L;
private Character currentChar = null;
private Double chance;
private TreeNode parentNode;
private TreeNode leftNode;
private TreeNode rightNode;
public TreeNode(char currentChar, double chance) {
this.currentChar = currentChar;
this.chance = chance;
}
public TreeNode(double chance) {
this.chance = chance;
}
public Character getCurrentChar() {
return currentChar;
}
public Double getChance() {
return chance;
}
public void setParentNode(TreeNode parentNode) {
this.parentNode = parentNode;
}
public TreeNode getParentNode() {
return parentNode;
}
public void setLeftNode(TreeNode leftNode) {
this.leftNode = leftNode;
}
public TreeNode getLeftNode() {
return leftNode;
}
public void setRightNode(TreeNode rightNode) {
this.rightNode = rightNode;
}
public TreeNode getRightNode() {
return rightNode;
}
}