哈弗曼压缩

哈弗曼压缩的原理在这就不多说了,相信大家都了解(不了解的看有关资料),在这只讨论一下哈弗曼压缩步骤:

 

 

哈夫曼压缩:

1.读文件,统计文件中每个字节出现的次数,并用一个数组来保存

2.把数组中的每一个下标作为节点的元素(即为读取到的字节),下标对应的元素作为节点元素出现的次数, 构造节点优先队列

 

3.根据节点优先队列构建哈树

4.得到每个叶节点(即为字节)的编码

5.读文件,得到有序字符串

6.将有序字符串保存到字节数组中

7.将字节数组中的字节写入到文件当中

 

 

 

压缩:例如,获得了文件中的所有字节的字符串
  

1.如文件中:abbcccddddeeeee   假设文件是a.txt;
  

2.读文件,将读到的字节保存到一个int[]数组中
  

3.创建一个优先级队列将数组中的元素进行排序。
  

4.获得哈夫曼树的编码为:(且将编码 存到一个字符串数组当中即strs[256])
      a="010";--------strs[97]="010";
      b="011";--------strs[98]="011";
      c="00";---------strs[99]="00";
      d="10";---------strs[100]="10";
      e="11";---------strs[101]="11";
  

5.再次读文件得到文件中所有字节的有序编码字符串
     

allString="010 011011 000000 10101010 111111111111";
   

6.将allString字符串每8位每8位保存到一个字节数组byte[] 当中   
   
       如:  byte[0]=77;
       byte[1]=-127;
       byte[2]=85;
       byte[3]=-1;
       byte[4]=-128;
       byte[5]=7;
    
   

7.写文件,把字节数组写入到文件当中  假设写入的文件是b.txt;
   
   
    ----------就完成压缩了!!!
   
    以下是压缩代码:
   

public class YaSuo {

	/**
	 * 步骤一: 读取文件,统计文件中每个字节出现的次数,将字节出现的次数用一个int[] num = new int[256]保存
	 * 数组的下标表示字节,下标对应的元素表示该字节出现的次数
	 * 
	 * @param path
	 *            要读取的文件路径
	 * @return 返回用来存储字节次数的数组
	 */
	public int[] readFile(String path) {
		// 创建一个数组用来保存读到的字节 下标i 0-255是所有的字节 t表示每个字节出现的频率
		int[] num = new int[256];

		try {

			// 创建文件输入流用来读文件
			FileInputStream fis = new FileInputStream(path);// 此处抛出异常

			// 把输入流包装成缓冲流
			BufferedInputStream bis = new BufferedInputStream(fis);

			// 用缓冲流读字节,并把读到的字节保存到数组当中以便获得每个字节的频率
			while (bis.available() > 0) {// 当剩余字节不为0时

				int t = bis.read();// 缓冲流读到不同的字节就会返回不同的整型即0-255

				num[t]++;// 当读到相同的字节时频率就加1

				// 將獨到的字節設置成節點元素
				HfmNode node = new HfmNode(t, num[t]);
				// System.out.println("節點元素:"+(Integer)node.obj);
			}
		} catch (Exception ef) {
			ef.printStackTrace();
		}
		return num;
	}

	/**
	 * 步骤二: 当字节数组中元素不为0的时候,就创建一个树的节点对象, 将节点对象使用一个优先对象存储,来保证节点按照字节出现的次数排序
	 * 
	 * @param arr
	 *            用来保存字节出现次数的数组
	 * @return 返回保存节点的优先队列
	 */
	public PriorityQueue<HfmNode> array2Queue(int[] arr) {
		// 创建排序队列,自己制定比较器
		PriorityQueue<HfmNode> nodeList = new PriorityQueue<HfmNode>(11,
				new MyComparator());
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] != 0) {// 如果出现的次数不是0,则创建节点
				// 创建节点对象
				HfmNode node = new HfmNode(i, arr[i]);
				nodeList.add(node);
			}
		}

		return nodeList;
	}

	// 实现比较器类
	class MyComparator implements Comparator<HfmNode> {// 按频率的大小进行比较
		public int compare(HfmNode h1, HfmNode h2) {
			return h1.getCount() - h2.getCount();

		}
	}

	/**
	 * 将优先级队列中的节点创建成哈夫曼树
	 * 
	 * @param nodeList
	 *            优先级队列
	 * @return 返回根节点
	 */
	public HfmNode createHfmTree(PriorityQueue<HfmNode> nodeList) {
		while (nodeList.size() > 1) {// 当优先级队列当中至少有两个节点时

			// 取出两个最小的节点
			HfmNode node1 = nodeList.poll();
			HfmNode node2 = nodeList.poll();
			// 有最小的节点创建合并的节点
			HfmNode addNode = new HfmNode(null, node1.getCount()
					+ node2.getCount());

			// 创建引用关系
			addNode.setLeft(node1);
			node1.setString("0");// 左节点编码为0

			addNode.setRight(node2);
			node2.setString("1");// 右节点编码为1

			node1.setLast(addNode);
			node2.setLast(addNode);
			// 将合并的节点加到优先队列当中
			nodeList.add(addNode);
		}

		HfmNode root = nodeList.peek();// 取出根结点

		return root;
	}

	/**
	 * 得到每个叶节点即字节的哈夫曼编码
	 * 
	 * 1.先得到每个叶节点的编码,将其保存在字符串数组中 如:String[] str=new String[256] 如str[97]="00011"
	 * str[98]="011111"
	 * 
	 * 
	 * 
	 */

	public String[] createByteCode(HfmNode root) {
		// 用一个字符串数组保存所有字符串编码如strs[97]="00011" str[98]="011111"
		// 如果定義為屬性的話,創建類對象時數組也會創建,浪費內存,所以把其放在方法中
		String[] strs = new String[256];
		Byte2String(strs, root);
		return strs;
	}

	private void Byte2String(String[] strs, HfmNode root) {

		if (root != null) {// 如果根節點不為空

			if (root.getLeft() != null) {// 如果左節點不為空,得到其編碼

				HfmNode leftNode = root.getLeft();// 得到左節點

				String str1 = root.getString() + leftNode.getString();// 左節點的編碼是父節點加自身節點

				leftNode.setString(str1);// 重新設置左節點編碼
				// System.out.println("獨到的字節"+(Integer) leftNode.getObject());
				// 將左節點的字節和編碼保存到字符串數組當中
				// 注意:是把葉節點的元素加進去,而不是所有創建樹的所有節點
				if (leftNode.getObject() != null) {
					// System.out.println("葉節點元素:"+(Integer)leftNode.getObject()+"葉節點字符串編碼:"
					// +leftNode.getString());
					strs[(Integer) leftNode.getObject()] = leftNode.getString();
				}
				// 遞歸調用
				Byte2String(strs, leftNode);
			}
			if (root.getRight() != null) {// 右節點不為空
				HfmNode rightNode = root.getRight();// 得到右節點
				// System.out.println("右節點的編碼:"+rightNode.getString());
				// 重新設置右節點編碼,即其編碼為父節點編碼加自身編碼
				String str2 = root.getString() + rightNode.getString();

				rightNode.setString(str2);
				// 將右節點的字節和編碼保存到字符串中
				if (rightNode.getObject() != null) {
					// System.out.println("葉節點元素:"+(Integer)rightNode.getObject()+"葉節點字符串編碼:"
					// +rightNode.getString());
					strs[(Integer) rightNode.getObject()] = rightNode
							.getString();
				}
				// 遞歸調用
				Byte2String(strs, rightNode);

			}
		}

	}

	/**
	 * 需要再次读文件,将文件中的所有字节按顺序用字节对应的编码表示,得到字符串 String allString=""; 如
	 * allString="0000010010101111100110101001111";
	 * 
	 * @param strs
	 * @return
	 */
	public String readAgain(String[] strs, String path) {
		// 文件中所有字節按順序對應的編碼
		String allString = "";
		try {
			// 创建文件输入流用来读文件
			FileInputStream fis = new FileInputStream(path);// 此处抛出异常

			// 把输入流包装成缓冲流
			BufferedInputStream bis = new BufferedInputStream(fis);

			// 用缓冲流读字节,并把读到的字节保存到数组当中以便获得每个字节的频率
			while (bis.available() > 0) {// 当剩余字节不为0时

				int t = bis.read();// 缓冲流读到不同的字节就会返回不同的整型即0-255

				// 存放从文件中读到的所有有序的字节编码
				allString = allString + strs[t];

			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		return allString;

	}

	/**
	 * 将有序的字符串转化成字节
	 * 
	 * @param allString
	 *            所读的文件中字节转化成的字符串
	 * @return 返回转化的字节
	 */

	// 将有序的字符串保存到数组当中
	public byte[] toByte1(String allString){
		
		//先定义字节数组的长度
		int len=allString.length()/8+1;
		if(allString.length()%8!=0){
			len++;
			
		}
		//创建字节数组
		
		byte[] b=new byte[len];
		
		//判断字节数组最后一位的字节
		if(allString.length()%8==0){
			b[len-1]=0;
		}else{
			int length=8-allString.length()%8;
			b[len-1]=(byte) length;
			
			int buling=8-allString.length()%8;
			for(int i=0;i<buling;i++){
				allString=allString+"0";
			}
		}
		
		// 将字符串转化成字节保存到字节数组当中
		for (int i = 0; i < len-1; i++) {
			String duanString = allString.substring(i * 8, i * 8 + 8);
			b[i] = (byte)(Integer.parseInt(duanString, 2));
//System.out.println(duanString+"<>"+b[i]);
		}
		//System.out.println("字节数组的长度"+b.length);
		return b;
		
	}
	
	
	/**
	 * 写入文件的方法       输出流
	 * @param path  写入的文件路径
	 * @param bs    要写入的数据
	 */
	public static void writeFile(String path,byte[] bs){
		//方法分析:在写文件方法中,例如,在记事本上写数据(是字节),是从内存中将数据流出到记事本中
		 //步骤:1.建立一个文件输出流对象,指明要输出到的文件路径,相当于一个管道,连接内存和写的文件
		//       即java.io.FileOutputStream fos=new java.io.FileOutputStream(path
		//2.将内存中的数据写出来,因为事先在方法中已经定义路径和写的数据是字节数组类型
//		                   即//for(int i=0;i<bs.length;i++){
//    	              byte b=bs[i];
//    	             //写出字节
//    	           //问题write(byte[] b)
//                  //有的write()方法,并不是一个节一个字节的写出,而是存在缓存区中,当满了就输出
//    	               fos.write(b);
//                     }
		//3.将缓冲区中的数据清空及关闭输出流fos.flush();fos.close()
		try{
		//创建文件输出流
		java.io.FileOutputStream fos=new java.io.FileOutputStream(path);
		//将字符串转化成字节数组
		//byte[] bs=content.getBytes();
		    for(int i=0;i<bs.length;i++){
		    	byte b=bs[i];
		    	//写出字节
		    	//问题write(byte[] b)
              //有的write()方法,并不是一个字节一个字节的写出,而是存在缓存区中,当满了就输出
		    	fos.write(b);
		    }
		    //清空数据(内存缓冲区的)
		    fos.flush();
		    //关闭输出流
		    fos.close();
		
		
		
		}catch(Exception df){
			df.printStackTrace();
		}
	}
	/**
	 * 步骤七:将有序字节数组中的字节写入到文件当中
	 * @param path  写入的文件路径
	 * @param b   字节数组:  写入的字节
	 */
	  public void writeFile1(String path,byte[] b){
		  try{
		  //创建文件输出流
		  FileOutputStream fos=new FileOutputStream(path);
		
		  BufferedOutputStream bos=new BufferedOutputStream(fos);
		  
		  //遍历字节数组中的字节
		  
			  //有的write()方法,并不是一个字节一个字节的写出,而是存在缓存区中,当满了就输出
		  for(int i=0;i<b.length;i++){
		    	bos.write(b[i]);
		  }
		  bos.flush();//清空的是缓冲流中的东西,而不是输出流中的东西。注意啊!!!!!!!!!!!
		  fos.close();
		  }catch(Exception e){
			  e.printStackTrace();
		  }
		  
	  }
	
	
}

 

 

感悟:老师给我们讲了哈弗曼压缩原理,让自己写一个压缩软件,一开始不知道怎么入手,一点思路都没有,在老师的耐心指导下, 才真正理解哈弗曼压缩原理,在方法上也要遵循每写一个方法都要测试一下正确性,否则到最后一起测试的时候,找不出到底是哪个方法出错了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值