本节主要是利用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-->