Java实现霍夫曼编码对文件解压缩
压缩流程:
1.将文本转换为字节数组,计算字节频率。扫描一个字节,频率+1
2.根据字节频率建立霍夫曼树,用最小堆。每次取最小的两个,加入最小堆中。
3.根据霍夫曼树遍历,左一次为0,右一次为1。为字节数组编新的码。字节数组和码一起生成压缩的
4.输出压缩文件
解压流程:
1.根据霍夫曼树,按压缩的码遍历,0为左遍历,1为右遍历,到叶子了就是新的码,每次都要重头遍历霍夫曼树
2.输出解压文件。
霍夫曼树节点类定义
private static class HuffmanNode implements Comparable<HuffmanNode> {byte data;int frequency;HuffmanNode left, right;public HuffmanNode(byte data, int frequency) {this.data = data;this.frequency = frequency;}public HuffmanNode(int frequency, HuffmanNode left, HuffmanNode right) {this.frequency = frequency;this.left = left;this.right = right;}public boolean isLeaf() {return left == null && right == null;}@Overridepublic int compareTo(HuffmanNode other) {return this.frequency - other.frequency;}}
压缩代码
/*** 压缩文件* @param inputFile 输入文件路径* @param outputFile 输出文件路径*/public static void compress(String inputFile, String outputFile) throws IOException {byte [] fileData = readFile(inputFile);int [] frequenct = computeFrequency(fileData);HuffmanNode root = buildHuffmanTree(frequenct);String [] codes = new String[256];generateCodes(root,"",codes);BitOutputStream bitout = new BitOutputStream();for (byte b : fileData){String code = codes[b&0xFF];for (char c :code.toCharArray()){bitout.writeBit( c == '1');}}bitout.close();try(DataOutputStream out = new DataOutputStream(new FileOutputStream(outputFile))){//写入 字节频率for (int i = 0 ; i < 256 ; i ++){out.writeInt(frequenct[i]);}//写入实际位数out.writeInt(bitout.getBitCount());out.write(bitout.toByteArray());}}
读文件
//读文件public static byte[] readFile(String filePath) throws IOException {try(FileInputStream in = new FileInputStream(filePath);ByteArrayOutputStream out = new ByteArrayOutputStream()){byte [] buffer = new byte[4096];int bytesRead;while( (bytesRead = in.read(buffer)) != -1 ){out.write(buffer,0,bytesRead);}return out.toByteArray();}}
获取字节频率
//获取字节的频率public static int [] computeFrequency(byte [] fileDate){int [] frequency = new int[256];for (byte b : fileDate){frequency[b&0xFF]++;}return frequency;}
构建霍夫曼树
//构建霍夫曼树private static HuffmanNode buildHuffmanTree(int[] frequency) {PriorityQueue<HuffmanNode> queue = new PriorityQueue<>();// 创建叶子节点并加入优先队列for (int i = 0; i < 256; i++) {if (frequency[i] > 0) {queue.offer(new HuffmanNode((byte) i, frequency[i]));}}// 处理特殊情况:空文件或只有一个字节的文件if (queue.isEmpty()) {return new HuffmanNode((byte) 0, 0);}if (queue.size() == 1) {HuffmanNode node = queue.poll();return new HuffmanNode(node.frequency, node, null);}// 合并节点直到只剩一个根节点while (queue.size() > 1) {HuffmanNode left = queue.poll();HuffmanNode right = queue.poll();HuffmanNode parent = new HuffmanNode(left.frequency + right.frequency, left, right);queue.offer(parent);}return queue.poll();}
生成霍夫曼编码
//生成霍夫曼编码public static void generateCodes(HuffmanNode node,String code,String [] codes){if(node.isLeaf()){codes[node.data & 0xFF] = code;}else{generateCodes(node.left,code +"0",codes);generateCodes(node.right,code +"1",codes);}}
按位读写流
private static class BitOutputStream {private ByteArrayOutputStream out = new ByteArrayOutputStream();private int currentByte;private int bitCount;private int totalBits;public void writeBit(boolean bit) {currentByte = (currentByte << 1) | (bit ? 1 : 0);bitCount++;totalBits++;if (bitCount == 8) {out.write(currentByte);currentByte = 0;bitCount = 0;}}public void close() {// 填充剩余的位if (bitCount > 0) {currentByte <<= (8 - bitCount);out.write(currentByte);}}public byte[] toByteArray() {return out.toByteArray();}public int getBitCount() {return totalBits;}}
//位输入流 - 用于按位读取数据private static class BitInputStream {private byte[] data;private int currentByteIndex;private int currentBitIndex;private int totalBits;private int bitsRead;public BitInputStream(byte[] data, int totalBits) {this.data = data;this.totalBits = totalBits;}public boolean readBit() {if (bitsRead >= totalBits) {return false;}if (currentBitIndex == 8) {currentByteIndex++;currentBitIndex = 0;}boolean bit = (data[currentByteIndex] & (1 << (7 - currentBitIndex))) != 0;currentBitIndex++;bitsRead++;return bit;}}
解压
/*** 解压文件* @param inputFile 输入文件路径* @param outputFile 输出文件路径*/public static void decompress(String inputFile, String outputFile) throws IOException {try (DataInputStream in = new DataInputStream(new FileInputStream(inputFile))) {// 读取频率表int[] frequency = new int[256];for (int i = 0; i < 256; i++) {frequency[i] = in.readInt();}// 重建霍夫曼树HuffmanNode root = buildHuffmanTree(frequency);// 读取压缩数据int bitCount = in.readInt();byte[] compressedData = new byte[in.available()];in.readFully(compressedData);// 解码数据ByteArrayOutputStream output = new ByteArrayOutputStream();HuffmanNode current = root;BitInputStream bitIn = new BitInputStream(compressedData, bitCount);for (int i = 0; i < bitCount; i++) {boolean bit = bitIn.readBit();current = bit ? current.right : current.left;if (current.isLeaf()) {output.write(current.data);current = root;}}// 写入解压文件try (FileOutputStream out = new FileOutputStream(outputFile)) {out.write(output.toByteArray());}}}
测试
public static void main(String[] args) {try {String inputFile = "E:\\DemoTest\\test.txt";String compressedFile = "E:\\DemoTest\\test_compressed.huff";String decompressedFile = "E:\\DemoTest\\test_decompressed.txt";// 压缩long startTime = System.currentTimeMillis();compress(inputFile, compressedFile);long compressTime = System.currentTimeMillis() - startTime;// 解压startTime = System.currentTimeMillis();decompress(compressedFile, decompressedFile);long decompressTime = System.currentTimeMillis() - startTime;// 计算压缩率File original = new File(inputFile);File compressed = new File(compressedFile);double ratio = (1 - (double) compressed.length() / original.length()) * 100;System.out.println("压缩完成!");System.out.println("压缩时间: " + compressTime + "ms");System.out.println("解压时间: " + decompressTime + "ms");System.out.println("压缩率: " + String.format("%.2f", ratio) + "%");} catch (IOException e) {e.printStackTrace();}}
测试结果
压缩完成!
压缩时间: 1388ms
解压时间: 1563ms
压缩率: 29.73%