`
GLC
  • 浏览: 110817 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

哈弗曼简单实现压缩

阅读更多

哈弗曼简单实现压缩

了解哈弗曼树就先从简单的二叉树讲起:
   二叉树:有一个根结点及其叶子结点组成、一个结点下只能接两个叶子结点:分别叫做左子树和右子树;其存储信息和链表的结点大致相似。
如图所示:



[size=large]
二叉树的遍历可分为四种:先序遍历、中序遍历、后序遍历以及层次遍历。

  先序遍历:先访问根结点再访问左子树、右子树。

  中序遍历:先访问左子树、再访问根结点、右子树。

  后序遍历:先访问左子树、右子树、根结点。

  层次遍历:一层一层从上往下、从左往右遍历。

  区分先、中后序遍历的最好方法是根据左子树遍历的先后;而且左子树始终在右子树前面。

了解二叉树后、我们需要知道权值的概念:在数据结构中,权值就是定义的路径上面的值。

例如长沙,广州分别是两个端点,两个端点可以用边相连表示可达,长沙到广州800km,那么就说边的权值是800.




这样、二叉树通过赋权值使得其形成的权值不同;而我们做哈弗曼压缩时、需要的是权值最小

的树即最优树。
例:




这样、压缩所需要的哈弗曼树有了,接着我们要遍历压缩的内容并用数组统计其出现的频率、

统计完后、通过数组中统计的频率构造哈弗曼树;在构造过程中、生成好的二叉树将叶子节点

以左0右1的方式分配编码;再用数组存装对应字符及其相应哈弗曼编码、压缩时,读入头文

件、将数组中哈弗曼编码长度写入压缩文本、接着将每个字符的对应的哈弗曼编码连接;当大

于8个字节时化成整形写入文件;最后不足8个时需要补0.这就是压缩头文件。。。
例:

// 创建文件输入流对象
		FileInputStream fis = new FileInputStream(path);
		// //创建文本缓冲流对象
		// BufferedInputStream bis=new BufferedInputStream(fis);
		// //实例化一个缓冲输出流对象
		// BufferedOutputStream bos=new BufferedOutputStream(fos);
		// 实例化一个输出流对象
		FileOutputStream fos = new FileOutputStream(path + "压缩文件.txt");

		// 写出每一个字节对应编码的长度
		for (int j = 0; j < saveCode.length; j++) {
			// System.out.println("请输出对应节点的长度" + saveCode[j]);
			// if (saveCode[j].getLength() != 0) {
			// 先 写入asc码所对应的下表
			// fos.write(j);
			// 写入asc码中每个编码的01串长度
			fos.write(saveCode[j].getLength());
			System.out.println("字符"+((char)j)+"的编码:" + "------->"
					+ saveCode[j].getNode());
			// 将01串的长度进行累加
			reminder += saveCode[j].getLength();
			// }
		}
//		// 计算出补零的个数,并读入
//		int remind = reminder % 8;
//		// 写入文件,并将remind进行强转
//		fos.write(remind);
//		System.out.println("jjjjjjjjjjjjjjj" + remind);
		/*********************** 写入头文件 **************************************/

		// 先声明asc编码的下表
		int i = 0;
		// 声明缓冲区得到的01串编码
		String writes = "";
		// 声明要转的01串字符
		String waiteString = "";
		// 声明一个01串的中转站
		String tranString = "";
		// 声明01串的个数
		int count = 0;
		// 进行循环,将sac编码下所有01串字符转化成int型写入文件
		while ((i < 256) || (count >= 8)) {
			// 如果缓冲区的01串大于8// 如果大于等于8,则写入文件
			if (count >= 8) {
				// 先将waiteString清零
				waiteString = "";
				// 先取出前8个01串字符
				for (int j = 0; j < 8; j++) {
					waiteString += writes.charAt(j);
				}

				// 将取出的01串转化为int型,且写入文件
//				System.out.println("><><><><><><" + waiteString);
//				
//				System.out.println("&&&&&&&&&&&&&"+changeString(waiteString));
				// 判断writes的01串是等于还是大于8
				if (writes.length() > 8) {
					//清空中转字符
					tranString="";
					// 将writes的01串减8
					for (int t = 8; t < writes.length(); t++) {
						// 将剩下的01串移到tranString上
						tranString += writes.charAt(t);
					}
                    //再将writes清空
					writes="";
					// 再将writes指针指回剩下的01串
					writes = tranString;
				}
				// 如果是等于8
				else {
					writes = "";
				}
				// 再将count数量减8
				count -= 8;
				//定义一个变量来接收转化成int型的数
				int intw=changeString(waiteString);
				fos.write(intw);
			// 如果01串不足8个
			}else {
				// System.out.println("--------->");
				// 判断对应asc编码是否存在
				// if(saveCode[i]!=null){
				// 得到01串的个数
				count += saveCode[i].getLength();
				// 得到01串编码
				writes += saveCode[i].getNode();
//				System.out.println(saveCode[i].getLength() + "<><><><><><><><>"
//						+ saveCode[i].getNode());
				// }
				System.out.println("i:"+i+">>>>>>>>>>"+saveCode[i].getNode());
				i++; 
			}
		}
		System.out.println("跳出循环");
		// 最后循环累加之后、剩下不足8个01串,则进行补0

		if (count > 0) {
			// 定义一个计数器;记录补零的个数
//			int k = 0;
			waiteString = "";// 清空要转化的的码
			for (int t = 0; t < 8; t++) {
				if (t < writes.length()) {
					waiteString = waiteString + writes.charAt(t);
//					System.out.println("**********************" + t);
				} else {
//					k++;
					waiteString = waiteString + '0';
				}
			}
			fos.write(changeString(waiteString));// 写入
			// 写入头文件补0 的个数
			// fos.write(k);
//			System.out.println("llllllllllll" + k);
			System.out.println("写入了->" + waiteString);
		}



压缩文件内容:一边遍历文件内容、一边将读到的字节一个一个地转成哈弗曼编码、再连接;

当大于8个01串长度时就进行转化;并将转化后的int型写入。写入之后要记得删除以转化的

01串;这样转化直至文件末尾;如果文件末尾01串长度不足8位、则需要补0;并将补0的个

数写入文件;

/*************** 写入文件内容 ************************************************/
		// 得到读入字符的ascii编码的值
		int idata = fis.read();
//		System.out.println("文件内容是:" + idata);
		// 缓冲区得到的01串编码
		writes = "";
		// 要转的01串字符
		tranString = "";
		// 01串的个数
		count = 0;
		// 文件没有读完的时候
		while ((fis.available() > 0) || (count >= 8)) {
			// 如果缓冲区的01串码大于等于8,则进行转化
			if (count >= 8) {
				// 先将waiteString清零
				waiteString = "";
				// 截取前8个01串并转化
				for (int t = 0; t < 8; t++) {
					waiteString += writes.charAt(t);
				}
				// 将取得的01串转化并写入
//				System.out.println("转化的字符是:" + waiteString);
				// 判断01串是大于8还是等于8
				if (writes.length() > 8) {
					// 01串的中转站
					tranString = "";
					// 再将01串减8
					for (int t = 8; t < writes.length(); t++) {
						tranString += writes.charAt(t);
					}
					writes="";
					// 将writes指针指回
					writes = tranString;
					System.out.println("打印出剩下的01串:" + writes);
				} else {
					// 如果等于8
					writes = "";
				}
				// 将前八个01串删除
				count -= 8;
				int intw=changeString(waiteString);
				fos.write(intw);

			} else {
				// 读入idata字节,对应编码写出信息

				count = count + saveCode[idata].getLength();
				// 将字符所对应的01串编码进行累加
				writes += saveCode[idata].getNode();
//				System.out.println("<---------------->" + count + "===="
//						+ writes);
				// 将idata往下移
				idata = fis.read();
//				System.out.println("//////" + saveCode[idata].getLength()
//						+ "//////" + saveCode[idata].getNode());
			}
		}
		// 再将读取的字符的01串长度进行累加
		count += saveCode[idata].getLength();
		// 将01串进行累加
		writes = writes + saveCode[idata].getNode();

		// 记录补零的个数
		int endsint = 0;
		if (count > 0) {
			// 清空待转化的字符
			waiteString = "";
			// 将剩下的01串转到waiteString上
			for (int t = 0; t < 8; t++) {
				if (t < writes.length()) {
					waiteString = waiteString + writes.charAt(t);
				} else {// 剩下的补零
					waiteString = waiteString + '0';
					endsint++;
				}
			}
			// 补完0 后,进行转化写入
			fos.write(changeString(waiteString));
			System.out.println("最后的字符已经写入了!!!!");
		}
//		System.out.println("ttttttttttt" + endsint);
		// 将补零的个数写入
		fos.write(endsint);
		System.out.println("压缩完毕。。。。");
		fis.close();
		fos.close();



解压的过程是一个逆反过程:也是先读出头文件、再读出文件内容并读出的内容转成01串与读出的头文件匹配、、如果匹配成功则写入文件、值得注意的是倒数第二个有补0的情况、这就需要将最后一位补零个数的字节读出来;减去。这样再看是否能继续转化。。。

java.io.FileInputStream fis = new java.io.FileInputStream(path2);
		// 实力化输入流对象
		java.io.FileOutputStream fos = new java.io.FileOutputStream(path2 + "解压文件.txt");
		// 读入文件,得到asc码数组的值
		for (int i = 0; i < 256; i++) {
			// 先读出asc码下表
			Code code = new Code();
			// 读出每个ascii码的01串长度
			code.setLength(fis.read());
			code.setNode("");
			// 加入到数组中
			codeArray[i] = code;
			// System.out.println("--------->"+i+codeArray[i].getLength());
		}
//		int reminder = fis.read();
//		System.out.println("编码长度读取完毕" + reminder);
		/********************** 读入头文件 **********************/

		// 解压头文件,得到 sacii码的01串//读SaveCode中0~256字节的的数据
		// 声明一个从文件中读出的com变量
		String coms = "";
		// 声明一个从0开始计数的变量
		int i = 0;
		while (i < 256) {
			// 当缓存区的编码比codeArray[i]的长度长时
			if (coms.length() >= codeArray[i].getLength()) {
				// System.out.println("================="+i+":"+coms+"<>"+codeArray[i].getLength());
				// 取得该编码
				for (int t = 0; t < codeArray[i].getLength(); t++) {
					// 将coms上的字符转到codeArray[i]的node中
					codeArray[i].setNode(codeArray[i].getNode()
							+ coms.charAt(t));
				}
//				System.out.println("现在"+((char)i)+"的编码是:" + codeArray[i].getNode());
				// 去掉coms前几位
				String coms2 = "";
				for (int j = codeArray[i].getLength(); j < coms.length(); j++) {
					coms2 += coms.charAt(j);
				}
				coms="";
				// 将coms指针回指
				coms = coms2;
				 System.out.println("<><><><><><><>"+i+codeArray[i].getNode());
				// 往后移
				
				i++;
				
			}
			// 如果没有写入的话,再读入一个byte化为01串
			else {
				// System.out.println("-------->"+coms);
				coms = coms + intToString(fis.read());
				// System.out.println("<><><><><><><><><><><"+coms);
			}


[/size]


如果浏览还不清楚、可以下载源代码;谢谢指教
  • 大小: 121.5 KB
  • 大小: 72.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics