java实现哈夫曼树的压缩与解压

 

下方链接为用 java 实现哈夫曼树:

https://blog.csdn.net/www_chinese_com/article/details/88070625

目录

一、压缩

二、解压


一、压缩

利用哈夫曼编码对文件进行压缩和解压的大概步骤如下

(1)读取文档中的所有字符,在长度为256的int型数组(以下取名为 ascii)中记录相应字符出现的次数,下标为字符的ASCII码值

(2)将数组中值不为0的项构成二叉树(权值为该项的数值,字符为该项下标ASCII码值对应的字符),将二叉树存储在结点数组中

(3)将结点数字中的结点进行排序并创建一棵哈夫曼树

(4)为了能快速找到相应字符所对应的哈夫曼编码,创建一个哈希表,key 值为 ascii 数组中值不为0的为项的下标,value 为该 key 值字符的哈夫曼编码。

(5)根据文件中的字符串提取出每一个字符,在哈希表中找到对应编码,将编码以 String 类型进行储存。

(6)将解压时需要用到的数据存进压缩文件中

(7)将得到的编码,每 32 个字符为一组,将其装换为 int 型数据存入压缩文件中。如果得到的编码字符数不是 32 的倍数,可以在编码的全面或后面补 0 ,再进行上述操作,不过要将补 0 个数写进文件中。

TreeNode root;// 树的头结点
File inputFile;// 要进行压缩的文件
File outputFile;// 压缩成的文件
String source = "";// 文件中所有的字符,用于编码
String allCode = "";// 记录文件中全部字符的编码
int[] ascii = new int[256];// 存放str中各个字符出现的次数(除中文外)
ArrayList array = new ArrayList();// 将存在的字符的树节点存在array中,用于排序
HashMap map = new HashMap();// 存储字符及其哈夫曼编码

/**
 * 构建一个Haffuman树
 * 
 * @param str
 *            用于构建Huffman树的数据
 */
public Huffman(File file) {
	this.inputFile = file;
	collect();// 记录str中字符的出现次序
	setArrayList();// 将其存入链表中
	sort();// 将其进行排序
	// 将链表中的结点
	TreeNode tNode = null;
	while (array.size() > 1) { // 当链表中的结点数大于1个的时候,将结点不断的加入到哈夫曼树中

		tNode = product(array.get(0), array.get(1));
		array.remove(0);// 移除原本链表中最小的两个结点
		array.remove(0);
		array.add(0, tNode);
		sort();// 再将新的链表进行排序

	}
	root = tNode;

	setHashMap(root, ""); //(4)
	getAllCode(); //(5)
	saveCode(); // (6) (7)

}

分解步骤:

(1)读取文档中的所有字符,在长度为256的int型数组(以下取名为 ascii)中记录相应字符出现的次数,下标为字符的ASCII码值

	/**
	 * 堆str中的字符遍历,ascii存放各个字符出现的次数
	 * 
	 * @param str
	 *            用于构建Huffman树的数据
	 */
	public void collect() {

		Reader reader;
		try {
			reader = new FileReader(inputFile);
			int n = reader.read();// 读取文件中的字符
			while (n != -1) {
				char c = (char) n;// 得到文件中字符的ASCII值
				source = source + c;
				// System.out.println("压缩类collect中的文件中字符c = " + c + " n = " + n);
				ascii[n]++;// 将对应位置的次序加一
				n = reader.read();
			}

			reader.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

(2)将数组中值不为0的项构成二叉树(权值为该项的数值,字符为该项下标ASCII码值对应的字符),将二叉树存储在结点数组中

/**
	 * 将ascii中不为0的数放进链表中
	 */
	public void setArrayList() {

		for (int i = 0; i < ascii.length; i++) {// 将ascii数组遍历一遍

			if (ascii[i] != 0) { // 当其中的值不为0
				TreeNode tNode = new TreeNode(new Node(ascii[i], (char) i + ""));
				array.add(tNode);
			}
		}

	}

(3)将结点数字中的结点进行排序并创建一棵哈夫曼树

	/**
	 * 根据权值(链表中存放的数据),将ascii代表的字符从小到大进行排序
	 */
	public void sort() {
		// 认为i前面的数据都是有序的,从第二个数开始遍历,
		// 记住开始的位置,记住开始的结点,当遇到第一个比它小的数时,
		// 检验此时的位置j是否和开始的位置相同,若相同,则不用变化
		// 若不相同,删除上一次的位置,将这个结点插入到现在这个位置
		for (int i = 1; i < array.size(); i++) {
			int min = i;
			int j;
			TreeNode tNode = array.get(i);
			for (j = i; j > 0; j--) {
				TreeNode lastNode = array.get(j - 1);
				if (tNode.node.pow > lastNode.node.pow)
					break;// 找到第一个比他小的数据时。退出循环
			}
			min = j;
			if (min != i) {// 如果,min值发生变化,即前面有比pow大的数
				array.remove(tNode);
				array.add(min, tNode);
			}
		}

	}

(4)为了能快速找到相应字符所对应的哈夫曼编码,创建一个哈希表,key 值为 ascii 数组中值不为0的为项的下标,value 为该 key 值字符的哈夫曼编码。

/**
	 * 得到每一个字符对应的哈夫曼编码,再将编码存储到HashMap中(递归)
	 * 
	 * @param tNode
	 *            根结点
	 * @param code
	 *            这个结点对应的编码
	 */
	public void setHashMap(TreeNode tNode, String code) {

		if (tNode.left != null) {
			setHashMap(tNode.left, code + "0");

		}
		if (tNode.right != null) {

			setHashMap(tNode.right, code + "1");
		}
		if (tNode.left == null && tNode.right == null)
			map.put(tNode.node.c, code);

	}

(5)根据文件中的字符串提取出每一个字符,在哈希表中找到对应编码,将编码以 String 类型进行储存。

	/**
	 * 得到文件中全部字符的全部编码
	 */
	public void getAllCode() {
		// 将在Huffman中得到的文件的内容全部翻译为编码
		for (int i = 0; i < source.length(); i++) {

			String key = source.charAt(i) + "";// 得到每一个字符
			String value = map.get(key);// 得到每一个字符的编码
			allCode = allCode + value;
		}
	}

(6、7)储存相关数据到压缩文件中

/**
	 * 将哈夫曼编码存起来
	 */
	public void saveCode() {
		// 存入字符及字符对应的编码, 存入补零个数,存入所有字符的编码

		outputFile = new File("src\\Tree\\SaveInformation");
		OutputStream out;
		try {
			// 创建输出流对象
			out = new FileOutputStream(outputFile);
			DataOutputStream dout = new DataOutputStream(out);
			// 将键值数写进压缩文件中
			int size = map.size();// map中的键值数
			dout.writeInt(size);
			// 将HashMap写进文件中
			Iterator iterator = map.keySet().iterator();
			while (iterator.hasNext()) {// 得到map中的内容
				String key = (String) iterator.next();
				String value = map.get(key);
				dout.writeUTF(key);
				dout.writeUTF(value);

			}

			int addZero = 0;// 添加0的个数
			if (allCode.length() % 32 != 0) {

				addZero = 32 - allCode.length() % 32;
				for (int i = 0; i < addZero; i++)// 给得到的编码补零(在前面加上)
					allCode = "0" + allCode;
			}
			// 写入补零个数
			dout.writeInt(addZero);
			// 每32位为一个int进行输入
			for (int i = 0; i < allCode.length(); i += 32) {

				char[] dst = new char[32];
				allCode.getChars(i, i + 32, dst, 0);// 得到32个字符形成的字符数组 (用这个方法不可以,出错了)

				// for (int j = 0; j < 32; j++) { //依次取出32位编码
				// dst[j] = allCode.charAt(i + j);
				// }

				// 对字符数组进行处理,算出int值,写入文件
				// int型第0位为符号位,为避免出错,从第1位开始
				int intCode = 0;
				for (int j = 1; j < 32; j++) { 
					intCode = intCode * 2;
					intCode = intCode + (dst[j] - '0');
				}
				//对0号位上的字符进行判断,决定int型数据的正负
				if (dst[0] == '1')
					intCode = -intCode;
				dout.writeInt(intCode);
			}

			dout.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

 

二、解压

由于在压缩是利用数据流将数据存进文件中,因此同样的,用数据流将数据从压缩文件中读取出来,再将编码翻译成字符串即可。

	File sourceFile;// 需要解压的文件
	File objectFile;// 目标文件
	DataInputStream din;// 输入流
	Writer writer;// 字符输出流
	// 读取文件中HashMap中的内容,得到字符及其相应的编码,但是key值为编码,value值为字符
	HashMap map = new HashMap();

	public Decompress(File sourceFile) {
		this.sourceFile = sourceFile;// 得到目标文件
		try {
			din = new DataInputStream(new FileInputStream(sourceFile));// 实例化输入流对象
			objectFile = new File("src\\Tree\\DecompressingFile");// 构建解压的目标文件
			writer = new FileWriter(objectFile);// 实例化输出流对象
			operate(); //进行解压的操作方法
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

按存入的顺序将数据读取出来,在这里,我们同样需要将哈希表读取出来并创建一张新的哈希表,但是新创建的这张哈希表,key 值为字符的编码,value 值为字符,因为此时我们是通过编码找到字符。

/**
 * 得到HashMap表
 */
public void getHashMap() {
	try {
		int size = din.readInt();// 得到map的键值数
		for (int i = 0; i < size; i++) {
			String value = din.readUTF();// 得到字符
			String key = din.readUTF();// 得到字符对应的编码
			map.put(key, value);
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

将读取出来的 int 数据转变为二进制的方法

	public int[] change(int num) {

		int[] array = new int[32];// 将byte化为二进制后的结果存入这个数组中
		if (num < 0)
			array[0] = 1;
		else
			array[0] = 0;
		num = Math.abs(num);// 得到正数
		for (int i = 31; i > 0; i--) {
			array[i] = num % 2;
			num = num / 2;
		}
		return array;

	}

进行解压的相关操作的方法

你可能感兴趣的:(Java编码笔记)