文件的压缩

本节主要是利用huffman树的原理来对文件进行处理,从而达到压缩文件的效果。huffman树又称为最优二叉树。是带权路径最短的树。

先说说怎么建huffman树,其构造方法为:

1.根据给定的n个权值的结点,选出两个结点值最小的数作为左、右子树,这个二叉树的根结点为左、右结点的权值之和。

2.将新的权值加入到剩余结点中,删除原来的两个结点

3.重复1 2,直到最后只有一个结点为止。

Huffman压缩是一个无损的压缩算法。通过构建huffman树,可以得到每个叶结点的huffman编码,然后把源文件的字节用huffman编码来存储,从而达到减小空间占用,实现压缩。

要实现文件的压缩,可以分为如下几步:

一、根据传入的文件,统计每个字节出现的次数

二、根据每个字节出现的次数,构建一棵huffman树

三、根据huffman树,得到每个字节的huffman编码

四、把源文件和huffman编码写入文件中去

通过这几步就基本上可以实现文件的压缩。

第一步:

 

/**
	 * 根据传入的文件,统计每个字节出现的次数
	 * @param path	文件的路径
	 * @return	int数组
	 */
	public int[] fileByteCount(String path){
		//存储文件中每个字节出现的次数
		try{
			FileInputStream fis=new FileInputStream(path);
			BufferedInputStream bis=new BufferedInputStream(fis);
			while(fis.available()>0){
				//文件是否读完
				int i=fis.read();
				byteCount[i]++;	//用数组存储每个字节出现的次数		
			}
			fis.close();
			bis.close();
		}catch(Exception ef){
			ef.printStackTrace();
		}	
		return byteCount;
	}

 第二步:

/**
	 * 创建最优二叉树的方法
	 * @param arr:传入的数组
	 * @param TreeNode:返回根结点
	 */
	public TreeNode createHuffman(int[] arr){
		//实例化一个优先队列对象
		PriorityQueue<TreeNode> queue = new PriorityQueue<TreeNode>(11,new MyComparator());
		TreeNode root=null;
		//遍历数组,将数组元素构建成结点,并添加到队列中
		for (int i=0;i<arr.length;i++){
			if(arr[i]!=0){
				//创建结点,结点值不为0
				TreeNode node = new TreeNode(i,arr[i]);
				//将结点添加到队列中
				queue.add(node);
			}
		}
		//遍历队列,每次取出两个最小元素作为叶子结点,它们的和作为父结点建立二叉树,
		//并从队列中删除这两个元素,同时把它们的父结点添加到队列中
		while(queue.size()>1){
			//同时取两个最小元素
			TreeNode node1 = queue.poll();
			TreeNode node2 = queue.poll();			
			//根据取得的元素创建父结点
			TreeNode fnode = new TreeNode(0,node1.getZijie()+node2.getZijie());			
			//建立引用关系
				fnode.setLeft(node1);
				fnode.setRight(node2);
				node1.setParent(fnode);
				node2.setParent(node2);		
			//将父结点添加到队列中
			queue.add(fnode);				
		}	
		root =queue.peek();
		return root;
	}	

 第三步:

/**
	 * 根据根结点创建哈夫曼编码
	 * 规则:左子结点为:1,右子结点为:0
	 * @param root:根结点
	 */
	public void createCode(TreeNode root,String code){
		//首先是确定各结点的路径
			//得到子结点
			TreeNode left = root.getLeft();	
			TreeNode right = root.getRight();
			//打印叶子节点
			if (left==null&&right==null){				
				System.out.println(root.getObj()+"的编码为:"+code);
				//byteCode 的下标是字节,值为其哈夫曼编码
				byteCode[(Integer)root.getObj()]=code;
			}
			if (left!=null){
				//递归
				createCode(left,code+"0");
			}
			if (right!=null){
				//递归
				createCode(right,code+"1");
			}
	}

 第四步:是最重要的一步,也是最关键的一步:

/**
	 * 把源文件和huffman编码写入文件中去
	 * @param path	源文件路径
	 * @param path1	要写入的文件路径
	 * @param str	huffman编码的String数组
	 */
	public void writeToFile(String path,String path1,String []str){
		try{
			FileOutputStream fos = new FileOutputStream(path1);
			DataOutputStream Dos = new DataOutputStream(fos);// 创建输出流	
			for (int k = 0; k< str.length; k++) {
				if (str[k] != null){ 
					Dos.writeInt(k);
					Dos.writeInt(str[k].length());	
				}else{
					str[k]="";
					Dos.writeInt(k);
					Dos.writeInt(0);
				}			
			}	
			//先把编码写入文件
			int count=0;//记录中转的字符个数
			int i=0;//第i个字节
			String writes="";
			String tranString ="";
			String waiteString ;
			while(i<256||count>=8){
				//等待的字符大于8
				if(count>=8){
					waiteString="";
					//讲writes前8位取出
					for (int t = 0; t < 8; t++) {
						waiteString = waiteString + writes.charAt(t);
					}
					// 将writes前八位去掉
					if (writes.length() > 8) {
						tranString = "";
						for (int t = 8; t < writes.length(); t++) {
							tranString = tranString + writes.charAt(t);
						}
						writes = "";
						writes = tranString;
					}else {
						//刚好只有8位
						writes = "";
					}
					count = count - 8;// 写出一个8位的字节
					int intw = changeString(waiteString);// 得到String转化为int
					// 写入文件
					Dos.writeInt(intw);
				}else if(count<8){
					// 得到第i个编码信息等待写入
					if (str[i]!= null ) {
						count = count + str[i].length();
						if(str[i].length()!=0){
						}
						writes = writes + str[i];
						i++;
					}
				}
			}
			// 把所有编码没有足够8的整数倍的String补0使得足够8的整数倍,在写入
			if (count > 0&&count<8) {
				waiteString = "";// 清空要转化的编码
				int buzero=0;
				for (int t = 0; t < 8; t++) {
					if (t < writes.length()) {
						waiteString = waiteString + writes.charAt(t);
					} else {
						waiteString = waiteString + "0";
						//补了多少个0
						buzero++;	
					}
				}
				Dos.writeInt(changeString(waiteString));
				Dos.writeInt(buzero);
			}
			try{
				// 将源文件中的所有byte转化为01字符串,写入压缩文件
				FileInputStream fis = new FileInputStream(path);
				BufferedInputStream bis = new BufferedInputStream(fis);
				count =0;
				writes="";
				tranString ="";
				int idata=bis.read();
				while(bis.available()>0||count>=8){
					//如果缓冲区等待写入的字符超过8
					if(count>=8){
						waiteString="";
						for (int t = 0; t < 8; t++) {
							waiteString = waiteString + writes.charAt(t);
						}
						// 将前八位删掉
						if (writes.length() > 8) {
							tranString = "";
							for (int t = 8; t < writes.length(); t++) {
								tranString = tranString + writes.charAt(t);
							}
							writes = "";
							writes = tranString;

						} else {
							writes = "";
						}
						count = count - 8;// 写出一个8位的字节
						int intw = changeString(waiteString);
						Dos.writeInt(intw);
					}else {
						// 如果不够8位,就继续取下一个字节
						count = count + str[idata].length();
						writes = writes + str[idata];
						idata = bis.read();
					}					
				}
				count = count + str[idata].length();
				writes = writes + str[idata];
				// 把count剩下的写入
				int endsint = 0;
				if (count > 0) {
					waiteString = "";// 清空要转化的码
					for (int t = 0; t < 8; t++) {
						if (t < writes.length()) {
							waiteString = waiteString + writes.charAt(endsint);
						} else {
							waiteString = waiteString + '0';
							endsint++;
						}
					}
					Dos.writeInt(changeString(waiteString));// 写入所补的0;
					Dos.writeInt(endsint);
					System.out.println(endsint);
					Dos.flush();
				}
				fis.close();
				bis.close();
				fos.close();
				Dos.close();
			}catch(Exception ef){
				ef.printStackTrace();
			}
		}catch(Exception ef){
				ef.printStackTrace();
		}
	}

 然后根据源文件的路径,调用方法,并制定生成的文件路径及文件名,这样就可以进行文件压缩啦。但这只是简单的压缩,可能对一些小文件进行压缩时,压缩的文件比源文件可能还大,这样还不如不压缩,这里可能还存在一些小问题,希望牛人指出。

 

 

 

<!--EndFragment-->

你可能感兴趣的:(文件)