`
笑揽清溪月
  • 浏览: 14336 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

哈夫曼压缩软件的制作

阅读更多
一、关于压缩的基础知识

引用
压缩软件是利用算法将文件有损或无损地处理,以达到保留最多文件信息,而令文件体积变小的应用软件。压缩软件一般同时具有解压缩的功能。压缩软件的的基本原理是查找文件内的重复字节,并建立一个相同字节的"词典"文件,并用一个代码表示,比如在文件里有几处有一个相同的词"中华人民共和国"用一个代码表示并写入"词典"文件,这样就可以达到缩小文件的目的。常见的压缩软件有WinRAR ,好压(Haozip),WinZip,7-Zip,WinMount,Peazip等等。

哈夫曼树作为数据结构二叉树章节中最为华彩的一部分,有着其独特的魅力。给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,这样的二叉树便是哈夫曼树,也称为最优二叉树。
       
二、关于哈夫曼算法

引用
  Huffman算法是一种基于统计的压缩方法。它的本质就是对文本文件中的字符进行重新编码,对于使用频率越高的字符,其编码也越短。但是任何2个字符的编码, 是不能出现向前包含的。也就是说字符A(假设为00)的编码的前段,不可能为字符B(则B的编码不可能为001,因为这里B的编码中包含了A的前段00,这会给解码难带来不必要的困难,所以这是不允许的)的编码。经过编码后的文本文件,主要包含2个部分:Huffman码表部分和压缩内容部分。解压缩的时候,先把Huffman码表取出来,然后对压缩内容部分各个字符进行逐一解码,形成源文件。
        哈夫曼编码生成步骤:
        ①扫描要压缩的文件,对字符出现的频率进行计算。
        ②把字符按出现的频率进行排序,组成一个队列。
        ③把出现频率最低(权值)的两个字符作为叶子节点,它们的权值之和为根节点组成一棵树。
        ④把上面叶子节点的两个字符从队列中移除,并把它们组成的根节点加入到队列。
        ⑤把队列重新进行排序。重复步骤③④⑤直到队列中只有一个节点为止。
        ⑥把这棵树上的根节点定义为0(可自行定义0或1)左边为0,右边为1。这样就可以得到每个叶子节点的哈夫曼编码了。




三、开发的大体思路

压缩:
        1、将要压缩的文件一个一个字节的读出来即扫描要压缩的文件,并统计每个字节的权值即出现的频率。
        2、以每个字节的权值来构造哈夫曼树,并给每个字节进行哈夫曼编码。
        3、将每个字节和其对应得哈夫曼编码存放进一个Map中,即码表。
        4、以这个码表为依照,将文件中的所有字节一一进行编码(生成10字符串),最后在把所有字节的编码依次末尾相加合成一个10字符串。
        5、将这个10字符串重新组合,8个为一组,若最后一组不足8个则补0,并记录补0的个数,将每一组的10字符串转化为一个字节,
并将所有的10字符串合成一个字节数组,数组的最后一个元素存放补0的个数。
        6、创建一个压缩文件,先将码表的大小写入文件,再将码表写入文件(码表里还有每个字节的哈夫曼编码长度的信息)。
        7、最后将之前生成的字节数组写入文件(文件的主要信息)。

        解压缩:
        1、将压缩的文件同样一个一个字节的读出来。
        2、先读出码表的大小,再通过码表的大小读出码表,并将码表的信息存放进一个Map。
        3、再接着读出后面的所有字节,并转化成一个10字符串。
        4、通过与码表的匹配,从10字符串的第一个字符开始读,若读到的子字符串与码表的某个字节的的编码相同,解压出相应的字节,把该字节保存起来。
并把上面的子字符串从编码中删除,重复上一步骤,直到该项编码解析完成,最后将此10字符串还原成原来的文件的一个个字节。
        5、再将这些字节写入一个新的文件,后缀名改成和原来文件一样,就能打开了。


1、压缩文件

/**
 * 压缩的文件操作
 * 
 * @author king
 * 
 */
public class CompressFileOption {

	/**
	 * 读取文件
	 * 
	 * @param path
	 *            :文件路径
	 * @return:将文件的内容以字节数组的样式返回
	 */
	public static byte[] readFile(String path) {
		byte[] dataByte = null;
		try {
			java.io.FileInputStream fis = new java.io.FileInputStream(path);
			int size = fis.available();// 可读的字节数
			dataByte = new byte[size];
			fis.read(dataByte);

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return dataByte;
	}

	/**
	 * 将码表的相关信息写入文件
	 * 
	 * @param fileSize
	 *            :原文件大小
	 * @param map
	 *            :存放码表的map
	 * @param listCh
	 *            :存放关键码的字符队列
	 * @param path
	 *            :文件路径
	 * @throws Exception
	 */
	public static void writeMap(int fileSize,
			java.util.HashMap<Byte, String> map, List<Byte> listBy, String path)
			throws Exception {

		java.io.FileOutputStream fos = new java.io.FileOutputStream(path);
		java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);

		dos.writeInt(fileSize);// 将原文件大小写入文件
		int mapSize = map.size();// 码表的大小
		dos.writeInt(mapSize);// //将码表的大小写入文件
		for (int i = 0; i < mapSize; i++) {
			fos.write(listBy.get(i));// 将每个字节写入文件
			String hfmcode_next = map.get(listBy.get(i));// 得到每个字节对应的哈夫曼编码
			byte codeSize = (byte) hfmcode_next.length();// 每个字节对应的哈夫曼编码大小
			fos.write(codeSize);// 将每个字节对应的哈夫曼编码大小写入文件
			dos.writeChars(hfmcode_next);// 将每个字符对应的哈夫曼编码写入文件
		}
		dos.flush();
		fos.close();
	}

	/**
	 * 将压缩好的字节数组写入文件
	 * 
	 * @param b
	 *            :压缩好的字节数组
	 * @param path
	 *            :文件路径
	 */
	public static void writeFile(byte[] b, String path) {

		try {
			java.io.FileOutputStream fos = new java.io.FileOutputStream(path,
					true);
			java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);
			// 写入字节数组的大小
			dos.writeInt(b.length);
			fos.write(b);
			fos.flush();
			fos.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 将10字符串转化为一个字节
	 * 
	 * @param str
	 *            :传入的字符串
	 * @return:一个字节
	 */
	private byte CharArrayToByte(String str) {
		char[] c = str.toCharArray();// 将字符串str转化为字符数组c
		int len = c.length;
		byte[] b = new byte[len];
		byte value = 0;
		byte value_next;
		for (int i = 0; i < len; i++) {
			b[i] = Byte.parseByte(c[i] + "");
			// System.out.println(b[i]);
		}
		for (int i = 0; i < len; i++) {
			value_next = (byte) (b[i] * Math.pow(2, len - i - 1));// 幂计算
			value = (byte) (value + value_next);
		}
		return value;
	}

	/**
	 * 将10字符串以8个为一组转化为一个字节数组
	 * 
	 * @param str
	 * @return
	 */
	private byte[] StringToByteArray(String str) {
		char[] c = str.toCharArray();// 将字节串str转化为字符数组c
		int len = c.length;// 字符串字符的个数
		int lenByte;
		String s = "";
		char c_next;
		byte[] b;
		if (len % 8 == 0) {// 如果字符串的长度能被8整除
			lenByte = len / 8 + 1;
			b = new byte[lenByte];
			for (int i = 0; i < lenByte - 1; i++) {
				for (int j = i * 8; j < (i + 1) * 8; j++) {
					c_next = c[j];
					s = s + c_next;
				}
				System.out.println("第" + i + "个字符串:" + s);
				b[i] = CharArrayToByte(s);
				s = "";
				System.out.println("第" + i + "个字符串转化为字节后的值:" + b[i]);
			}
			b[lenByte - 1] = 0;// 字节数组的最后一个存放补0的个数
		} else {// 如果字符串的长度不能被8整除

			lenByte = len / 8 + 2;
			b = new byte[lenByte];
			int remainder = len % 8;// 求出除8的余数
			int zeroNum = 8 - remainder;// 补0的个数
			System.out.println("补0数:" + zeroNum);
			System.out.println("原字符串:" + str);
			for (int i = 0; i < zeroNum; i++) {
				str = str + '0';// 在字符串后面补0
			}
			System.out.println("补0后的字符串:" + str);
			c = str.toCharArray();
			System.out.println("补0后的字符串的字符个数:" + c.length);
			for (int i = 0; i < lenByte - 1; i++) {
				for (int j = i * 8; j < (i + 1) * 8; j++) {
					c_next = c[j];
					s = s + c_next;
				}
				System.out.println("第" + i + "个字符串:" + s);
				b[i] = CharArrayToByte(s);
				s = "";
				System.out.println("第" + i + "个字符串转化为字节后的值:" + b[i]);
			}
			b[lenByte - 1] = (byte) zeroNum;// 字节数组的最后一个存放补0的个数
		}
		return b;
	}

	/**
	 * 压缩文件
	 * 
	 * @param path1
	 *            :原文件路径
	 * @param path2
	 *            :压缩后的文件路径
	 * @throws Exception
	 */
	public void CompressFile(String path1, String path2) throws Exception {
		// 从文件中得到字节数组
		byte[] b = CompressFileOption.readFile(path1);
		int b_size = b.length;// 原文件大小
		byte[] b_compress;// 字节数组,存放压缩的字符串

		String hfmcode = "";// 文件内所有字节的哈夫曼编码
		String hfmcode_next;// 文件中每个字节的哈夫曼编码
		// 计算字符串中每个字节的权值,并返回一个存放字节和它对应权值的节点队列
		List<TreeNode> list = calWeight.calweight(b);
		int size = list.size();
		Huffman hfm = new Huffman();
		// 构建哈夫曼树并返回根节点
		TreeNode root = hfm.createHuffman(list);
		// 创建哈夫曼编码使其与字符一一对应
		hfm.createHfmCode(root, "");

		java.util.HashMap<Byte, String> map = hfm.getMap();// 得到码表
		List<Byte> listBy = hfm.getList();// 得到存放关键码队列

		System.out.println("mapsize---->:" + map.size());
		System.out.println("b---->:" + b.length);
		for (int i = 0; i < b_size; i++) {
			// 得到每个字节的哈夫曼编码
			hfmcode_next = map.get(b[i]);
			System.out.println("第"+i+"个: " + b[i] + "的编码:" + hfmcode_next);
			hfmcode = hfmcode + hfmcode_next;// 将每个字节的哈夫曼编码依次相加为一个01字符串
		}
		System.out.println("01串大小:" + hfmcode.length());
		System.out.println("01串:" + hfmcode);
		char[] ch = hfmcode.toCharArray();
		System.out.println("01串的大小:" + ch.length);

		b_compress = StringToByteArray(hfmcode);// 得到字节数组
		for (int i = 0; i < b_compress.length; i++) {
			System.out.println("第" + i + "个字节" + b_compress[i]);
		}
		// 将文件大小和码表相关信息写入文件
		writeMap(b_size, map, listBy, path2);
		// 将字节数组写入文件
		writeFile(b_compress, path2);
	}


2、解压缩


/**
 * 解压缩的文件操作
 * 
 * @author king
 * 
 */
public class UncompressFileOption {

	public static long fileSize;

	/**
	 * 将8位10字符串前面缺0的补上0
	 * 
	 * @param str
	 * @return
	 */
	private String addZero(String str) {
		int strLen = str.length();
		int zeroNum;
		if (strLen < 8) {// 若字符串长度小于8则补0
			zeroNum = 8 - strLen;
			for (int i = 0; i < zeroNum; i++) {
				str = "0" + str;
			}
		}
		return str;
	}

	/**
	 * 将整型数组还原成之前的10串,即文件内容的哈夫曼编码
	 * 
	 * @param n
	 * @return
	 */
	private String InttoBinaryString(int[] n) {
		int len = n.length;
		String[] s = new String[len];// 一个字符串数组存放二进制数据
		String BinaryStr = "";
		for (int i = 0; i < len - 1; i++) {
			s[i] = Integer.toBinaryString(n[i]);
			s[i] = addZero(s[i]);
			BinaryStr = BinaryStr + s[i];
		}
		System.out.println("二进制形式表示:" + BinaryStr);
		int BinaryStrLen = BinaryStr.length();// 得到为减0前的字符串大小
		int zeroSub = n[len - 1];// 之前在末尾补0的个数,现在减去
		System.out.println("减0前的字符串大小:" + BinaryStrLen);
		System.out.println("需要在字符串末尾减0的个数表示:" + zeroSub);
		BinaryStr = BinaryStr.substring(0, BinaryStrLen - zeroSub);
		System.out.println("减0后的字符串大小:" + (BinaryStrLen - zeroSub));
		System.out.println("减0后的二进制形式表示:" + BinaryStr);
		return BinaryStr;
	}

	/**
	 * 字符串匹配,判断字符串child是否为parent的前子串
	 * 
	 * @param parent
	 * @param child
	 * @return
	 */
	private boolean StringMatch(String parent, String child) {

		char[] p = parent.toCharArray();
		char[] c = child.toCharArray();
		// System.out.println("数组p的长度:" + p.length);
		// System.out.println("数组c的长度:" + c.length);
		boolean b = false;
		for (int i = 0; i < c.length; i++) {
			if (c[i] == p[i]) {
				b = true;
			} else {
				b = false;
				break;// 有一个字符不匹配则跳出循环
			}
		}
		return b;
	}

	/**
	 * 解压缩文件
	 * 
	 * @param path2
	 *            :压缩后的文件路径
	 * @param path3
	 *            :解压缩后的文件路径
	 * @throws Exception
	 */
	public void UncompressFile(String path2, String path3) throws Exception {

		HashMap<Byte, String> map = new HashMap<Byte, String>();
		java.io.FileInputStream fis = new java.io.FileInputStream(path2);
		java.io.DataInputStream dis = new java.io.DataInputStream(fis);

		java.io.FileOutputStream fos = new java.io.FileOutputStream(path3);

		fileSize = dis.readInt();// 得到原文件的大小
		int mapSize = dis.readInt();// 得到码表的大小
		byte[] mapKey = new byte[mapSize];// 创建一个字符数组,存放码表中的字节
		byte codeSize;// 每个字节对应的哈夫曼编码大小
		String hfmcode_next = "";
		// 读取码表内容
		for (int i = 0; i < mapSize; i++) {
			mapKey[i] = (byte) fis.read();// 得到第i个字节
			codeSize = (byte) fis.read();// 得到每个字节对应的哈夫曼编码大小
			char[] codeChar = new char[codeSize];
			for (int j = 0; j < codeSize; j++) {
				codeChar[j] = dis.readChar();
				hfmcode_next = hfmcode_next + codeChar[j];
			}
			map.put(mapKey[i], hfmcode_next);// 将键值对放入Map中
			hfmcode_next = "";
		}
		int len = dis.readInt();// 得到压缩好的字节数组的大小
		System.out.println("压缩好的字节数组的大小: " + len);
		byte[] b = new byte[len];// 字节数组,存放压缩的字节串
		int[] n = new int[len];// 整型数组
		fis.read(b);// 得到压缩好的文件的字节数组
		for (int i = 0; i < b.length; i++) {
			System.out.println("第" + i + "个字节:" + b[i]);
			// 将字节还原成原来的整型数据
			if (b[i] < 0) {
				n[i] = b[i] + 256;
			} else {
				n[i] = b[i];
			}
			System.out.println("第" + i + "个字节的整型表示:" + n[i]);
		}
		String formerStr = InttoBinaryString(n);// 得到原10串

		System.out.println("formerStr:" + formerStr);
		int size = map.size();
		System.out.println("mapsize:" + size);

		int endIndex = formerStr.length();
		System.out.println("endIndex:" + endIndex);
		int beginIndex;

		// byte[] fileData = new byte[fileSize];
		int count = 0;// 记录文件当前是第几个字节

		while (!formerStr.isEmpty()) {// 如果字符串不为空

			String child;
			for (int i = 0; i < mapKey.length; i++) {
				child = map.get(mapKey[i]);
				if (formerStr.isEmpty()) {// 若字符串为空
					break;
				}
				if (StringMatch(formerStr, child)) {// 若匹配
					// 一个字节字节的写入文件
					fos.write(mapKey[i]);
					count++;
					beginIndex = child.length();
					formerStr = formerStr.substring(beginIndex, endIndex);
					endIndex = endIndex - beginIndex;
				}
			}
		}
		fos.flush();
		fos.close();
	}


五、项目展示










六、项目感想

1、如今最大的缺陷就是相率问题还没有解决,一旦压缩大的文件就要等很久,甚至失去响应。
2、界面依旧很简陋,需要改进。

0
0
分享到:
评论
1 楼 if(i!=我){} 2012-10-31  
1、用了流,却不知道流是用来干什么的
2、缓冲、缓冲、缓冲!
3、哈夫曼算法对巩固树的相关知识很有帮助,但不是一个实用的压缩算法。想研究压缩,就从gzip或者deflate开始吧。

相关推荐

    运用哈夫曼编码压缩解压文件源代码

    哈夫曼编码压缩解压文件源代码 哈夫曼编码是一种变长前缀编码方案,用于压缩数据。哈夫曼树是一种特殊的二叉树,它的每个叶子结点对应一个字符,而每个内部结点对应一个符号的频率信息。哈夫曼编码的基本思想是:将...

    用haffman树做得压缩软件

    《哈夫曼编码在压缩软件中的应用——基于MFC实现》 哈夫曼编码是一种高效的数据压缩算法,它利用字符出现频率的差异进行编码,频繁出现的字符赋予较短的编码,不常出现的字符则分配较长的编码,以此达到压缩数据的...

    基于哈弗曼树,用C#写的压缩软件

    A软件名称:基于哈夫曼编码的文件压缩实用程序系统 B软件组成:WinZip.exe C制作平台及相关调试工具: Windows Xp sp3 Microsoft Visual Studio 2005 D运行环境: win 2K/win xp/win visita/win 7 安装有.net ...

    哈夫曼编译器

    哈夫曼编码是一种数据压缩方法,而编译器则是将高级语言转换为机器语言的软件。这里我们将分别介绍这两个概念。 哈夫曼编码(Huffman Coding)是一种基于频率的无损数据压缩算法,由大卫·哈夫曼于1952年提出。它的...

    哈夫曼编译码文件压缩器《数据结构作业》

    使用哈夫曼编码的算法写出的一个小东西。这个只是个Demo,代码在其他资源中

    mp3压缩工具(压缩10倍不失真)

    MP3压缩工具是一种用于减小音频文件大小的软件或算法,尤其在处理MP3格式的音频时,能够显著降低文件占用的存储空间,同时尽可能保持音质不变或损失极小。"压缩10倍不失真"这个概念意味着该工具或方法能够在不影响听...

    Huffman.zip

    一个简单的压缩软件,利用哈夫曼思想,构造哈夫曼编码,实现对文件的二进制压缩,以及解压,再利用MFC制作可视化操作界面,美化软件又简化文件操作,可到博客看详细说明:...

    用VB做的文件打包软件

    这个VB制作的文件打包软件虽然没有特定的标签,但我们可以推断它可能是面向普通用户,提供简单易用的打包和解压缩功能,同时也可能具备一些高级特性,如加密、分卷压缩等,以满足不同用户的需求。

    基于 C++ MFC 的文本压缩软件【100010875】

    通过这次哈夫曼编码译码器的制作,对 C++ 的 STL 又进一步了解了比如优先级队列,bitsit 类等等,也初探红黑树的强大,将 ACM 学到的算法在此得到了优化,还学习了 C++ 文件的定位,文件指针等等,遗憾的是没有太多...

    多媒体视频及压缩技术课件

    熵编码则采用哈夫曼编码、算术编码等方式进一步压缩数据。 3. 视频压缩标准:目前广泛应用的视频压缩标准有MPEG-1、MPEG-2、MPEG-4、H.264/AVC以及最新的H.265/HEVC。这些标准通过更高效的编码算法实现了更高的压缩...

    多媒体实验霍夫曼,算数编码

    实验涵盖声音信号的获取与处理、Photoshop图像处理、Flash动画制作、以及数据压缩技术——霍夫曼编码和词典编码(LZW)。 实验一【声音信号的获取与处理】中,学生将学习如何使用麦克风和录音软件来录制和编辑声音...

    MP3开发制作详细资料

    MP3开发制作是一个涵盖音频编码技术、编程语言应用、软件工程等多个领域的复杂过程。这份"MP3开发制作详细资料"包含了一系列的资源,旨在为对此领域感兴趣的学者提供全面且深入的学习指导。 首先,MP3是一种有损...

    Compression-software:文本压缩软件

    基于MFC的文本压缩软件 用到的主要算法和数据结构:霍夫曼树,DFS,堆,红黑树 文本压缩率:20%~30% 简单的MFC图形界面: 通过这次哈夫曼编码译码器的制作,对C++的STL又进一步了解了比如优先级队列,bitsit类等等,也...

    熊出没.zip

    选择合适的解压缩软件,然后选择文件,点击“解压”或“提取”选项,指定解压位置,即可将内容解压到指定文件夹。 5. 安全性与隐私:虽然ZIP文件方便了数据交换,但也可能成为病毒传播的载体。因此,在接收和打开...

    数据结构课设选题.pdf

    - 实际应用哈夫曼编码进行数据压缩和解压缩,需要理解编码和解码的过程。 通过以上项目,学生将深入理解数据结构的概念,熟练掌握各种数据结构的实现方法和操作,以及如何将这些理论知识应用于实际问题的解决方案...

    自制MP3的相关资料

    5. **熵编码**:量化后的数据通过熵编码(如哈夫曼编码或算术编码)进一步压缩,将数据组织成更高效的二进制序列,减少冗余信息。 6. **帧结构**:MP3文件由一系列帧组成,每个帧包含音频数据的一小部分以及元数据...

    图片存为jpeg(90KB)

    【标题】"图片存为jpeg(90KB)"所涉及的知识点主要集中在图像处理和文件存储格式上。...这些知识点不仅对于开发者,也对于从事多媒体内容制作、网页设计、软件开发等相关领域的人员都具有重要的实践意义。

    数据结构毕业设计题目整理.doc

    数据结构毕业设计题目整理 ...* 关键技术:哈夫曼算法、数据压缩技术 这些毕业设计题目涵盖了数据结构的多个方面,包括数组、链表、树、图形等数据结构,涵盖了文件操作、交互式系统设计、算法设计等技术。

    (1912制作)C语言笔试题集之3(103页)

    - 可以考虑使用哈夫曼编码或其他压缩算法来减少空间占用。 - 编写转换函数,将原始字符串转换为压缩格式。 #### 12. 改进栈数据结构 - **题目描述**:改进标准栈数据结构,增加一个`min()`函数,以便在常数时间内...

Global site tag (gtag.js) - Google Analytics