`
C_SHaDow
  • 浏览: 51652 次
  • 性别: Icon_minigender_1
  • 来自: 大同
社区版块
存档分类
最新评论

重回压缩

阅读更多

之前用Huffman编码做过一个压缩小程序。当时的Huffman树半自适应的,需要对源文件扫描两遍。这次是完全自适应的,只需要对源文件扫描一次就可以生成压缩文件,并且压缩文件中不会含编码表。具体关于原理的东西实在网上搜的文档(附件中有),C++的源代码网上也有。

以下是我的代码:(代码有错,代码有错,我是按那个文档并且参照C++的源代码做的,杯具杯具,路漫漫……)

package cn.cls.greedy;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.print.DocFlavor.CHAR_ARRAY;

import cn.cls.bean.HNode;

public class Huffman {

	// 常量--------------------------------------------------------------
	/**
	 * 编码的最大值
	 */
	private final int MAX_VALUE = 255;

	/**
	 * 首次出现数据的代替码
	 */
	private final int CODE_ESCAPE = 256;

	/**
	 * 用于扩展新节点
	 */
	private final int CODE_EXTAND = 257;

	/**
	 * 节点列表的大小
	 */
	private final int LIST_LENGTH = 258;

	/**
	 * 非叶子节点
	 */
	private final int NOT_DATA = -1;

	/**
	 * 字节最低位
	 */
	private final int BOTTOM_BIT = 0;

	/**
	 * 字节最高位
	 */
	private final int TOP_BIT = 7;

	/**
	 * 输出缓存区的大小
	 */
	private final int BUFFER_SIZE = 10240;

	/**
	 * 节点初始count值的左移量(加倍)
	 */
	private final int INIT_RATIO = 2;

	/**
	 * 每次重构树是节点count值右移量(减小)
	 */
	private final int SHRINK_RATIO = 1;

	// 变量--------------------------------------------------------------
	/**
	 * 源文件、目标文件
	 */
	private File src, des;

	/**
	 * 文件输入、输出流
	 */
	private FileInputStream ins;
	private FileOutputStream ous;

	/**
	 * 树的根节点、解压时当前指向的节点
	 */
	private HNode root, current;

	/**
	 * 输出流位指针、缓冲区指针
	 */
	private int bit_pointer, buffer_pointer;

	/**
	 * 输出缓冲区
	 */
	private byte[] outputBuffer;

	/**
	 * 节点列表
	 */
	private HNode[] list;

	// 构造器、初始化-------------------------------------------------------
	public Huffman(File src, File des) {

		this.src = src;
		this.des = des;
	}

	/**
	 * 初始化
	 */
	public int init() {

		// 初始化文件输入、输出流
		try {
			ins = new FileInputStream(src);
			ous = new FileOutputStream(des);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return -1;
		}

		// 初始化节点列表
		list = new HNode[LIST_LENGTH];
		for (int i = 0; i < list.length; i++)
			list[i] = null;

		// 初始化树
		HNode tmp, tmp2;
		root = newHNode(NOT_DATA, true, null);
		tmp = newHNode(CODE_ESCAPE, false, root);
		tmp2 = newHNode(CODE_EXTAND, true, root);
		root.lchild = tmp2;
		root.rchild = tmp;
		root.count = tmp.count + tmp2.count;
		current = root;
		
		// 初始化输出缓存区
		outputBuffer = new byte[BUFFER_SIZE];
		outputBuffer[0] = 0;
		buffer_pointer = 0;
		bit_pointer = TOP_BIT;

		return 0;
	}

	// 外部接口-----------------------------------------------------------
	/**
	 * 读取源文件并压缩
	 * 
	 * @return 0:操作成功
	 */
	public int encode() {

		// 初始化
		if (init() == -1) {
			return -1;
		}

		// 初始化输入缓存区
		int[] buffer = new int[BUFFER_SIZE];

		// 循环读取入缓存区
		int available = 0;
		try {
			while ((available = ins.available()) > 0) {
				// 如果剩余字节不足以填满缓存区,则新建最小缓存区
				if (available < BUFFER_SIZE) {
					buffer = new int[available];
				}

				// 读入缓存区
				int tmp;
				for (int i = 0; i < buffer.length; i++) {
					tmp = ins.read();
					buffer[i] = (tmp > 0) ? tmp : (tmp + 256);
				}

				// 对缓存区的数据编码
				int data;
				for (int i = 0; i < buffer.length; i++) {
					data = buffer[i];
					/*
					 * 如果该数据不在树中,则输出CODE_ESCAPE对应的编码(其实就是1),输出数据
					 * 并且在树中新建该字符的节点;如果在数据已存在与树中,则输出其对应的编码,
					 * 而且由于对树节点自底向上count加一,还需要对树进行必要的调整。
					 */
					if (list[data] == null) {
						encodeData(CODE_ESCAPE);
						for (int j = 7; j > 0; j--)
							writeBit(data & (int) Math.pow(2, j));
						enlargeHTree(data);
					} else {
						encodeData(data);
						balanceHTree(list[data].parent);
					}
					/*
					 * 对树进行瘦身,这一步是不要的,可以使你的压缩文件更小
					 * (源文件越大,效果越明显。但如果源文件数据变态,也不一定的哦!)
					 */
					shrinkHTree();
				}
			}
			flush();
		} catch (IOException e) {
			e.printStackTrace();
			return -1;
		}

		return 0;
	}

	/**
	 * 读取压缩文件并压缩
	 * 
	 * @return 0:操作成功
	 */
	public int decode() {

		return 0;
	}

	// 输入输出------------------------------------------------------------
	/**
	 * 输出一位,如果缓存区满了,就输出到文件
	 * 
	 * @param bit
	 *            位数据
	 * @return 0:操作成功
	 */
	private int writeBit(int bit) {

		// 写入一位数据
		if (bit != 0)
			outputBuffer[buffer_pointer] |= (int) Math.pow(2, bit_pointer);

		// 位指针向低位移动
		bit_pointer--;

		// 如果位指针越过字节最低位,则回归最高位,同时将缓冲区指针向后移动
		if (bit_pointer < BOTTOM_BIT) {
			bit_pointer = TOP_BIT;
			buffer_pointer++;

			// 如果缓存区指针越过缓存区,则输出缓存区,指针归零,清零缓存区第一个字节
			if (buffer_pointer == BUFFER_SIZE) {
				try {
					ous.write(outputBuffer);
					ous.flush();
				} catch (IOException e) {
					e.printStackTrace();
					return -1;
				}
				buffer_pointer = 0;
				outputBuffer[0] = 0;
			}

			// 清零缓存区下一字节
			outputBuffer[buffer_pointer] = 0;
		}

		return 0;
	}

	/**
	 * 输出缓存区
	 */
	private void flush() {
		try {
			ous.write(outputBuffer, 0, buffer_pointer);
			ous.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 自底向上遍历树,并将编码输出
	 * 
	 * @param data
	 *            数据
	 * @return 0:操作成功
	 */
	private int encodeData(int data) {

		if (data > CODE_ESCAPE) {
			System.out.println("编码时遇到未知数据!");
			return -1;
		}

		// 自底向上编码,并输出
		HNode tmp = list[data];
		do {
			writeBit((tmp.isleft ? 0 : 1));
			tmp.count++;
		} while ((tmp = tmp.parent) != root);

		return 0;
	}

	// 树操作------------------------------------------------------------
	/**
	 * 新建一个节点,并指定其父节点;同时初始化节点的count,并将节点放入节点列表
	 * 
	 * @param data
	 *            节点数据
	 * @param isleft
	 *            true:左节点
	 * @param parent
	 *            父节点
	 * @return 新建的节点
	 */
	private HNode newHNode(int data, boolean isleft, HNode parent) {

		HNode tmp = new HNode();
		tmp.data = data;
		tmp.isleft = isleft;
		tmp.parent = parent;

		// 对编码数据count赋初始值
		if (data <= MAX_VALUE)
			tmp.count = (int) Math.pow(2, INIT_RATIO) - 1;

		// 加入叶节点列表
		if (data != NOT_DATA)
			list[data] = tmp;

		return tmp;
	}

	/**
	 * 当出现的数据是树中没有的数据时,扩展树
	 * 
	 * @param data
	 *            节点数据
	 * @return 0:操作成功
	 */
	private int enlargeHTree(int data) {

		// 取出扩展节点
		HNode tmp = list[CODE_EXTAND].parent;

		// 生成非叶子节点,以扩展节点的父节点为父节点;将扩展节点接到此节点后
		HNode tmp2 = newHNode(NOT_DATA, true, tmp);
		tmp.lchild = tmp2;
		tmp2.lchild = list[CODE_EXTAND];
		list[CODE_EXTAND].parent = tmp2;

		// 生成数据节点,此节点为扩展节点的兄弟节点
		tmp = newHNode(data, false, tmp2);
		tmp2.rchild = tmp;

		// 自底向上调整count
		tmp2.count = 0;
		int tmp3 = tmp.count;
		do {
			tmp2.count += tmp3;
		} while ((tmp2 = tmp2.parent) != root);

		// 调整树
		balanceHTree(tmp.parent);

		return 0;
	}

	/**
	 * 当计数器由某个根节点的更新到根节点后,需要考虑调整部分树节点
	 * 
	 * @param node
	 *            调整的范围是node的父节点到node的子节点,深度范围为3
	 * @return 0:操作成功
	 */
	private int balanceHTree(HNode node) {

		HNode parent = node.parent;

		// 如果父节点为根节点则不调整
		if (parent == root) {
			return 0;
		}

		// 找出node的兄弟节点
		HNode brother = node.isleft ? parent.rchild : parent.lchild;

		// 找出node下count较大的子节点
		HNode child = node.lchild.count > node.rchild.count ? node.lchild
				: node.rchild;

		// 如果node的兄弟节点的count小于child的count,则交换
		if (child.count > parent.count - brother.count) {
			boolean isleft = child.isleft;
			if (node.isleft) {
				parent.rchild = child;
				child.isleft = false;
			} else {
				parent.lchild = child;
				child.isleft = true;
			}
			if (isleft) {
				node.lchild = brother;
				brother.isleft = true;
			} else {
				node.rchild = brother;
				brother.isleft = false;
			}
		}
		
		// 递归向上调整
		balanceHTree(parent);
		
		return 0;
	}

	/**
	 * 为了是树尽可能的小(编码尽可能的短),对树进行瘦身是必要的,删除出现频率很小的节点
	 * 
	 * @return 0:操作成功
	 */
	private int shrinkHTree() {
		
		HNode tmp, parent, brother;
		// 将所有数据节点count减倍,删除count为零的节点
		for (int i = 0; i <= MAX_VALUE; i++) {
			tmp = list[i];
			if (tmp!=null) {
				tmp.count >>= SHRINK_RATIO;
				if (tmp.count==0) {
					
					parent = tmp.parent;
					
					if (tmp.isleft) {
						brother = parent.rchild;
					} else {
						brother = parent.lchild;
					}

					brother.parent = parent.parent;
					if (parent.isleft) {
						parent.parent.lchild = brother;
						brother.isleft = true;
					} else {
						tmp.parent.rchild = brother;
						brother.isleft = false;
					}
			
					tmp = null;
					parent = null;
					list[i] = null;
				}
			}
		}
		
		return 0;
	}
}

 

Hnode:

package cn.cls.bean;

public class HNode {

		public int data;          //数据
		public int count;         //频率
		public HNode parent;      //父节点
		public HNode lchild;      //左子节点
		public HNode rchild;      //右子节点
		public boolean isleft;    //0为左节点,1为右节点
}

 

最后说明一下,解压的方法还没写,稍等,1h以后在下一篇日志里见。。。

1
0
分享到:
评论

相关推荐

    【数字信号处理】基于matlab小波变换的时间重分配多重同步压缩变换【含Matlab源码 2460期】.zip

    4. **小波重构**:最后,利用MATLAB的逆小波变换函数,将压缩后的系数重构回信号形式。 描述中提到,这个项目包含完整的MATLAB源代码,可以在2014a或2019b版本中直接运行。这意味着用户可以下载源码,通过实际操作...

    谷轮压缩机故障维修指南.pdf

    液击通常是由于停机时回气冷却型压缩机中的制冷剂迁移,或者空气冷却型压缩机的回液造成的。液击会引发阀片或曲轴损坏、排气阀片升程限制器固定螺松动或损坏等问题。维修液击的措施包括选用合适的压缩机、保证蒸发器...

    化工用离心式压缩机的转速调节

    离心式压缩机广泛应用于化工生产中压缩和输送各种气体,是重要的动力输送设备。这种机器主要分为两种驱动方式:一种以电动机驱动,另一种则以汽轮机驱动。离心式压缩机在运行过程中,压缩机的工艺参数可能会发生变化...

    基于pca的图像压缩与重建代码

    为了恢复图像,将压缩后的数据乘以对应的主成分,再进行逆标准化,最后可能需要将数据转换回原来的色彩空间。 8. **代码结构**: - `sample.jpg`:这是一个示例图片,用于测试PCA图像压缩与重建算法。 - `...

    irls_matlab图像处理_压缩感知_

    本文将深入探讨“irls_matlab图像处理_压缩感知_”这一主题,重点关注IRLS(迭代重加权最小二乘)算法在压缩感知中的应用。 首先,让我们了解压缩感知的基本概念。压缩感知(Compressive Sensing,CS)是一种信号...

    行业资料-建筑装置-废纸回收自动压缩分料池.zip

    "行业资料-建筑装置-废纸回收自动压缩分料池.zip"这一资料包,专注于介绍一种先进的建筑装置,即废纸回收自动压缩分料池,旨在提高废纸处理效率,降低资源浪费,同时减轻环境负担。下面,我们将深入探讨这一技术的...

    使用libcurl获取经过gzip压缩的网页文件

    在实际开发中,结合`curl_easy_setopt`函数,可以自定义更多选项来满足特定需求,例如设置超时时间、重试次数、用户代理字符串等。 通过以上步骤,我们可以使用libcurl有效地获取和处理经过gzip压缩的网页文件。...

    jpg文件解压缩并显示 jpg解压缩算法

    6. **重采样与上采样**:JPEG可能采用了不同的采样率来处理不同颜色通道,解压缩时需要进行相应的上采样操作,确保所有通道的像素数量相同。 7. **最后的显示**:完成上述步骤后,图像数据已经恢复成原始格式,可以...

    rar压缩软件.rar

    开关重定义。 你可以在同一命令行指定普通文件名和列表文件。如果文件和列表 文件都未被指定,那么 RAR 将默认是 *.*,来处理所有文件。 许多 RAR 命令,例如解压、测试和列表,都允许在压缩文件名中使用...

    JPEG压缩编码(基本系统)算法.rar_bmp 压缩_jpeg_jpeg 编码 _jpeg 解码_jpeg压缩算法

    7. JPEG解码:JPEG解码器接收压缩后的数据流,通过熵解码恢复量化系数,然后进行反量化和IDCT,将数据转换回空间域,最后重组成完整的图像。 8. 压缩比率:JPEG允许用户调整压缩级别,从而控制图像质量和文件大小...

    g729语音压缩解压源码

    5. **合成滤波**:通过合成滤波器生成音频样本,这个过程包括短时逆滤波和窗函数的应用,将参数转换回连续的音频信号。 在压缩包中的开发源码可能包含了实现上述过程的C或C++函数,可能还包括编译脚本和库文件,...

    用DSP进行mp3解压缩的完整方案

    频域重建则涉及傅里叶变换,将频域信息转换回时域信号。重采样根据需要调整采样率,反量化是将量化指数转换为幅度值。 在“2001MAR26_DSP_MSD_TAC.pdf”文档中,可能详细阐述了如何利用DSP芯片来执行这些操作。通常...

    mp3-compression-codec-source-code.rar_MP3压缩代码_mp3 codec

    - **逆离散余弦变换(IDCT)**:将频域信息转换回时域,得到近似的原始音频样本。 - **重建信号**:根据采样率和量化级,将量化后的样本恢复为连续的数字音频信号。 - **后处理**:可能包括反掩蔽、重采样等步骤...

    行业文档-设计装置-一种整体燃气式天然气压缩机提高压缩效率的方法.zip

    整体燃气式天然气压缩机是一种特殊类型的压缩设备,它直接利用天然气作为驱动介质,通过内部燃烧来产生动力,推动压缩机工作,减少了中间能量转换的损失,从而提高了整体工作效率。这种设计方法旨在优化压缩过程,...

    行业分类-设备装置-压缩麦捆砖及压缩麦捆砖墙体.zip

    《压缩麦捆砖及其在墙体应用》 压缩麦捆砖是一种新型的环保建筑材料,它源自农业废弃物的再利用,特别是农作物如小麦、稻谷等收割后的麦捆。这些麦捆经过特殊的压缩处理,转变成具有较高密度和稳定性的砖块,从而在...

    机械蒸汽再压缩(MVR)压缩机行业分析报告.docx

    全球市场的主要企业排名中,三一重工宜兴富曦、Hanwha Techwin、江苏乐科节能科技、江苏金通灵、ITO等企业表现出色,体现了中国公司在MVR压缩机技术上的竞争力。此外,国际巨头如GEA Wiegand、Atlas Copco、Gardner ...

    哈夫曼树的压缩程序及其效果

    压缩文件通常会包含文件头信息,如文件名、原始文件长度以及哈夫曼树的节点信息,以便在解压缩时重建树结构。在解压缩过程中,需要从压缩文件中读取这些信息,然后根据哈夫曼树的结构解析位流,将编码转换回原始字符...

    约克YCWS压缩机维修手册.doc

    操作和维护部分详细介绍了压缩机的拆卸、安装流程,如断开电源、回收制冷剂、抽真空、能量调节电磁阀的调整、容量控制活塞和卸载弹簧的维护、油加热器和油过滤器的更换、进气过滤器的清洁、转子座和排气座的检查、...

    钢铁行业周报:3月PMI重回扩张区间,经济企稳信心或增强.zip

    在【压缩包子文件的文件名称列表】中,"钢铁行业周报:3月PMI重回扩张区间,经济企稳信心或增强.pdf" 这个文件名与标题完全一致,表明这是一份详细的报告,可能会涵盖3月份钢铁行业的产量、需求、价格、库存、国内外...

Global site tag (gtag.js) - Google Analytics