Huffman编码算法实现文件压缩以及解压

Huffman编码算法详解与Java实现

1. 数据结构背景介绍

1.1 Huffman树基本原理

Huffman编码是一种变长编码方案,是一种典型的贪心算法应用。

核心思想:
  • 出现频率高的字符赋予较短的编码,出现频率低的字符赋予较长的编码
  • 通过构造带权路径长度最短的二叉树(即Huffman树)来实现最优前缀编码
  • 前缀编码:任何一个字符的编码都不是另一个字符编码的前缀,保证解码的唯一性
关键概念:
  • 权值:字符出现的频率
  • 带权路径长度(WPL):从根节点到所有叶节点的路径长度与相应叶节点权值的乘积之和
  • 最优二叉树:WPL最小的二叉树

1.2 应用场景

Huffman编码广泛应用于:

  • 数据压缩:ZIP、RAR、7-Zip等压缩软件
  • 图片格式:JPEG、PNG等图像编码标准
  • 通信领域:数据传输中的编码优化
  • 文本处理:各类文本文件的压缩存储

2. 代码实现详解

2.1 HFMTree.java - Huffman树构建与编码

2.1.1 树节点结构

首先看树节点定义:

class TreeNode {
    int data;          // 权值(出现频率)
    public TreeNode left;
    public TreeNode right;
    String c;          // 字符内容
    String code;       // Huffman编码

    public TreeNode(int data) {
        this.data = data;
    }
    public TreeNode(int data, String c) {
        this.data = data;
        this.c = c;
    }
}
2.1.2 数据读取与频率统计

public void readData(){
    File path=new File("需压缩文件路径");
    StringBuilder sb=new StringBuilder();
    try {
       BufferedReader bufferedReader=new BufferedReader(new FileReader(path));
        while((line=bufferedReader.readLine())!=null){
            sb.append(line).append("\n");
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    line=sb.toString();

    Map<String,Integer> map=new HashMap<>();
    for (int i = 0; i <line.length() ; i++) {
        char c=line.charAt(i);
        if(map.containsKey(c+"")){
            map.put(c+"",map.get(c+"")+1);
        }
        else{
            map.put(c+"",1);
        }
    }
    List<TreeNode> list=new ArrayList<>();
    for(Map.Entry<String,Integer> entry:map.entrySet()){
        TreeNode node=new TreeNode(entry.getValue(),entry.getKey());
        list.add(node);
    }
    creatTree(list);
}

逻辑分析

  1. 读取源文件内容
  2. 使用HashMap统计每个字符的出现频率
  3. 将每个字符及其频率封装为TreeNode节点
  4. 调用creatTree()构建Huffman树
2.1.3 Huffman树构建算法

public void sort(List<TreeNode> list){
    for (int i = 0; i < list.size()-1; i++) {
        for (int j = 0; j < list.size()-1-i; j++) {
            if(list.get(j).data>list.get(j+1).data){
                TreeNode temp=list.get(j);
                list.set(j,list.get(j+1));
                list.set(j+1,temp);
            }
        }
    }
}

public void creatTree(List<TreeNode> nodeList) {
    while (nodeList.size() > 1) {
        sort(nodeList);
        TreeNode left = nodeList.remove(0);
        TreeNode right = nodeList.remove(0);
        TreeNode node = new TreeNode(left.data + right.data);
        node.left = left;
        node.right = right;
        nodeList.add(node);
    }
    root=nodeList.remove(0);
}

构建步骤

  1. 排序:使用冒泡排序按权值从小到大排序节点列表
  2. 选择:每次取出权值最小的两个节点
  3. 合并:创建新节点,权值为两节点权值之和
  4. 插入:将新节点放回列表
  5. 循环:重复上述步骤直到只剩一个节点(根节点)
2.1.4 生成Huffman编码

public void setcode(TreeNode node, String code){
    if(node==null) return;
    if(node.left==null&&node.right==null){
        node.code=code;
        return; 
    }
    setcode(node.left,code+"0");
    setcode(node.right,code+"1");
}

编码规则

  • 左子树路径标记为"0"
  • 右子树路径标记为"1"
  • 递归遍历,到达叶节点时记录编码
2.1.5 数据压缩写入

public void bitToByte(StringBuilder sb)throws Exception{
    File file=new File("压缩文件路径");
    FileOutputStream fileOutputStream = new FileOutputStream(file);
    ObjectOutputStream oop=new ObjectOutputStream(fileOutputStream);
    
    int len=(8-sb.length()%8)%8;
    for (int i = 0; i < len; i++) {
        sb.append("0");
    }
    
    oop.writeInt(len);
    oop.writeObject(codemap);
    
    int count=sb.length()/8;
    for (int i = 0; i < count; i++) {
        String s=sb.substring(i*8,(i+1)*8);
        int data=Integer.parseInt(s,2);
        oop.write(data);
    }
    oop.close();
    System.out.println("压缩完成! 共写入 "+count+" 个字节,补零 "+len+" 位");
}

写入内容

  1. 补零长度(用于解码时去除填充)
  2. 编码表(字符到编码的映射)
  3. 压缩后的二进制数据

2.2 解压算法

2.2.1 数据读取与解码准备

public void decompress() {
    File file = new File("压缩文件路径");
    
    try {
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        
        int paddingLen = ois.readInt();
        System.out.println("补零数量: " + paddingLen);
        
        Map<String, String> codemap = (Map<String, String>) ois.readObject();
        System.out.println("编码表大小: " + codemap.size());
        
        Map<String, String> decodeMap = new HashMap<>();
        for (Map.Entry<String, String> entry : codemap.entrySet()) {
            decodeMap.put(entry.getValue(), entry.getKey());
        }

关键步骤

  1. 读取补零长度
  2. 读取编码表
  3. 构建反向映射:将编码作为key,字符作为value,便于快速查找
2.2.2 二进制数据转换

StringBuilder binaryStr = new StringBuilder();
int byteData;
while ((byteData = fis.read()) != -1) {
    String binary = Integer.toBinaryString(byteData);
    while (binary.length() < 8) {
        binary = "0" + binary;
    }
    binaryStr.append(binary);
}

System.out.println("读取的字节数据长度: " + binaryStr.length() + " 位");

if (paddingLen > 0) {
    binaryStr.delete(binaryStr.length() - paddingLen, binaryStr.length());
}

转换逻辑

  1. 逐个字节读取压缩数据
  2. 将每个字节转换为8位二进制字符串(不足8位前面补0)
  3. 去除末尾的补零
2.2.3 解码还原
4
StringBuilder result = new StringBuilder();
String currentCode = "";

for (int i = 0; i < binaryStr.length(); i++) {
    currentCode += binaryStr.charAt(i);
    
    if (decodeMap.containsKey(currentCode)) {
        result.append(decodeMap.get(currentCode));
        currentCode = "";
    }
}

System.out.println("\n========== 解压后的文本内容 ==========");
System.out.println(result.toString());
System.out.println("======================================");
System.out.println("解压完成! 原文长度: " + result.length() + " 个字符");

解码算法

  1. 逐位读取二进制字符串
  2. 累积当前编码
  3. 当累积的编码在decodeMap中找到匹配时,输出对应字符
  4. 清空当前编码,继续下一位

3. 算法步骤可视化

3.1 Huffman编码流程图

┌─────────────────────────────────────────────────────────┐
│                    Huffman编码流程                        │
└─────────────────────────────────────────────────────────┘

┌─────────────┐
│  读取源文件  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 统计字符频率 │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 创建叶节点   │
└──────┬──────┘
       │
       ▼
┌──────────────────────────┐
│ 循环:                    │
│ 1. 排序节点列表          │
│ 2. 取出最小两个节点      │
│ 3. 创建父节点(权值和)    │
│ 4. 插入回列表            │
│ 直到只剩一个节点(根)     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 递归生成编码 │ (左0右1)
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 文本转编码串 │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 补零对齐8位 │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 写入压缩文件 │
└─────────────┘

3.2 Huffman解码流程图

┌─────────────────────────────────────────────────────────┐
│                    Huffman解码流程                        │
└─────────────────────────────────────────────────────────┘

┌─────────────┐
│ 读取压缩文件 │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 读取补零长度 │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 读取编码表   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 构建解码映射 │ (编码→字符)
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 读取字节数据 │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 转二进制串   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 去除补零     │
└──────┬──────┘
       │
       ▼
┌──────────────────────────┐
│ 逐位匹配解码:             │
│ 累积编码 → 查表匹配 →     │
│ 输出字符 → 清空继续       │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ 输出还原文本 │
└─────────────┘

4. 时间/空间复杂度分析

4.1 编码阶段复杂度

步骤时间复杂度空间复杂度
字符频率统计O(n)O(k)
树构建排序O(k² log k)O(k)
树构建合并O(k log k)O(k)
生成编码O(k)O(k)
文本编码O(n)O(n)
总体O(n + k² log k)O(n + k)

注:n为文本长度,k为不同字符数

4.2 解码阶段复杂度

步骤时间复杂度空间复杂度
读取数据O(m)O(m)
构建解码映射O(k)O(k)
二进制转换O(m)O(m)
逐位解码O(m)O(n)
总体O(m)O(m + n)

注:m为压缩后数据长度,n为原文长度

4.3 与传统编码方式对比

编码方式编码长度压缩率适用场景
ASCII固定编码8位/字符通用文本
UTF-81-4字节/字符多语言文本
Huffman编码变长,频率相关高(20%-90%)数据压缩

Huffman编码优势

  • 理论上可证明是最优前缀编码
  • 对于有重复模式的数据压缩效果显著
  • 实现相对简单,易于理解

5. 性能测试

使用该代码对自身源文件进行压缩测试:

指标数值
原文字符数约2000+
压缩后字节数~(原文长度/2)
补零位数0-7
压缩率约40%-60%

6. 总结

Huffman编码作为经典的数据压缩算法,凭借其最优前缀编码特性和简洁的实现,至今仍在各种压缩场景中广泛应用。通过详细分析,我们可以看到:

  1. 贪心思想是Huffman算法的核心
  2. 树结构提供了高效的编码生成和解码路径
  3. 前缀编码保证了解码的唯一性
  4. 通过反向映射可以实现O(1)时间的字符查找
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值