这个网上发现的Huffuman编码Java实现在组织上相对简化,便于理解文件压缩过程:提取文件统计字符频度-根据字符频度创建huffman树-根据huffman树生成huffman可变字长无前缀编码-根据huffman编码对文件中的字符转化成二进制串-将huffman编码的二进制串(非固定8位,可变字长)转化成8位固定字节的字符并输出文件。

代码中对于Java数据类型的使用也值得参考。

package cn.hm;

import java.io.File;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.InputStream;  
import java.io.OutputStream;  
import java.util.ArrayList;  
import java.util.Comparator;  
import java.util.HashMap;  
import java.util.Iterator;  
import java.util.PriorityQueue;  
import java.util.Set;  
  
public class HFMCondense {  
	
	public class HFMNode {  
	    byte data;  //存储字节的数据域  
	    int value;  //字节出现的频率  
	    String code;//叶子结点的哈弗曼编码  
	    HFMNode lchild,rchild;//左右孩子的引用  
	    //只指定数据的构造体  
	    public HFMNode(byte data,int rate){  
	        this(data,rate,null,null);  
	    }  
	    //同时指定左右孩子的构造体  
	    public HFMNode(byte data,int value,HFMNode lchild,HFMNode rchild){  
	        this.data=data;  
	        this.value=value;  
	        this.lchild=lchild;  
	        this.rchild=rchild;  
	    }  
	}  
	
    public static void main(String args[]){  
        String file="D://tmp//ori.txt";  
        HFMCondense condense=new HFMCondense(); 
        HFMNode hfmTree=condense.HashMapToHFMTree(condense.readFiletoMap(file));  
        condense.HuffmanCoding(hfmTree, "");  
        System.out.println("开始压缩...");  
        long start=System.currentTimeMillis();  
        condense.CompressFile(condense.createByteArray(condense.FileToString(file)),"D://tmp//des");  
        System.out.println("压缩结束...用时:"+(System.currentTimeMillis()-start));  
    }  
    /** 
     * 读取将要被压缩的文件,统计每一个字符出现的频率,并将得到的内容存入HashMap中 
     * @param fileName 将要被压缩的文件 
     * @return 每一个字节数出现的频率所对应的HashMap 
     */  
    public HashMap<Byte,Integer> readFiletoMap(String fileName){  
        HashMap<Byte,Integer> hashMap=new HashMap<Byte,Integer>();  
        File file=new File(fileName);  
        if(file.exists()){  
            try{  
                InputStream in=new FileInputStream(file);  
                //创建与文件大小相同的字节数组  
                byte[] content=new byte[in.available()];  
                //读取文件  
                in.read(content);  
                //存入HashMap中  
                for(int i=0;i<content.length;i++){  
                    //如果表中存在某一个键  
                    if(hashMap.containsKey(content[i])){  
                        //获取该字节当前的键值  
                        int rate=hashMap.get(content[i]);  
                        //键值增大  
                        rate++;  
                        hashMap.put(content[i], rate);  
                    }  
                    //如果不存在某一个字节对象,则将它存入HashMap当中  
                    else{  
                        hashMap.put(content[i],1);  
                    }  
                }  
                in.close();  
            }catch(Exception e){  
                e.printStackTrace();  
            }     
        }  
        else{  
            System.out.println("文件不存在");  
        }  
        return hashMap;  
    }  
    /** 
     * 将HashMap中的元素取出来封装到哈弗曼树中,树中叶子结点保存的是HashMap中的每一个键值与频率 
     * @param map 读取的Map 
     * @return  哈夫曼树的根结点 
     */  
    public HFMNode HashMapToHFMTree(HashMap<Byte,Integer> map){  
        //得到存储键值的系  
        Set<Byte> keys=map.keySet();  
        //得到迭代器对象  
        Iterator<Byte> iter=keys.iterator();  
        //如果还有值  
        while(iter.hasNext()){  
            byte key=iter.next();//获取系中的键  
            int value=map.get(key);//得到该键出现的频率  
            //创建结点并将结点对象加入到队列当中  
            HFMNode node=new HFMNode(key,value);  
            nodeQueue.add(node);  
            nodeList.add(node);  
        }  
        //当所剩的结点数还大于两个  
        while(nodeQueue.size()>=2){  
            //得到键值频率最小的两个结点  
            HFMNode left=nodeQueue.poll();  
            HFMNode right=nodeQueue.poll();  
            //将这两个结点组合起来生成新的结点  
            HFMNode node=new HFMNode(left.data,left.value+right.value,left,right);  
            nodeQueue.add(node);  
        }  
        //获取队列中的最后一个结点作为根结点  
        HFMNode hfm=nodeQueue.poll();  
        return hfm;  
    }  
    /** 
     * 为生成的哈弗曼树进行编码,产生对应的哈弗曼编码表 
     * @param hfm  对应的哈弗曼树 
     * @param code 对应生成的哈弗曼编码 
     * @return 哈弗曼编码表 
     */  
    //创建一个新的哈弗曼编码表  
    HashMap<Byte,String> codeMap=new HashMap<Byte,String>();  
    public HashMap<Byte,String> HuffmanCoding(HFMNode hfm,String code){  
        //如果左子树不为空,则左子树编码加1  
        if(hfm.lchild!=null){  
            HuffmanCoding(hfm.lchild,code+"1");  
        }  
        //如果右子树不为空,则右子树编码加0  
        if(hfm.rchild!=null){  
            HuffmanCoding(hfm.rchild,code+"0");  
        }  
        //如果到达叶子结点,则将元素放入HashMap中生成哈弗曼编码表  
        if(hfm.lchild==null&&hfm.rchild==null){  
            codeMap.put(hfm.data,code);  
            hfm.code=code;  
        }  
        return codeMap;  
    }  
    /** 
     * 将哈弗曼编码转换成字符串 
     * @param fileName 读取的文件名 
     * @return 编码之后的哈弗曼字符串 
     */  
    public String CodeToString(String fileName){  
        File file=new File(fileName);  
        String codeString="";  
        //如果文件存在  
        if(file.exists()){  
            try{  
                InputStream in=new FileInputStream(file);  
                byte content[]=new byte[in.available()];  
                in.read(content);  
                int i=0;  
                int len=content.length;//得到文件的字节长度  
                int size=nodeList.size();//得到队列的长度  
                while(i<len){  
                    for(int j=0;j<size;j++){  
                        if(content[i]==nodeList.get(j).data){  
                            codeString+=nodeList.get(j).code;  
                            break;  
                        }  
                    }  
                    i++;  
                }  
                in.close();  
            }catch(Exception e){  
                e.printStackTrace();  
            }  
        }else {  
            System.out.println("文件不存在");  
        }  
        return codeString;  
    }  
    /** 
     * 将文件按照对应的哈弗曼编码表转成01字符串 
     * @param fileName 读入的文件名 
     * @return 转译后的字符串 
     */  
    public String FileToString(String fileName){  
        File file=new File(fileName);  
        String StringContent="";  
        //如果文件存在  
        if(file.exists()){  
            try{  
                InputStream in=new FileInputStream(file);  
                byte content[]=new byte[in.available()];  
                in.read(content);  
                //循环转译  
                int len=content.length;  
                for(int i=0;i<len;i++){  
                    StringContent+=codeMap.get(content[i]);  
                }  
                in.close();  
            }catch(Exception e){  
                e.printStackTrace();  
            }  
        }else{  
            System.out.println("文件不存在");  
        }  
        return StringContent;  
    }  
    /** 
     * 将转译后的01字符串重新转换后放入新的字节数组当中 
     * @param code 转译后的01字符串 
     * @return 新的字节数组,里面包含了压缩后的字节内容 
     */  
    public byte[] createByteArray(String code) {  
        //将每8位字符分隔开来得到字节数组的长度  
        int size=code.length()/8;  
        //截取得到字符串8整数后的最后几个字符串  
        String destString=code.substring(size*8);     
        byte dest[]=destString.getBytes();    
        //s用来记录字节数组的单字节内容  
        int s = 0;  
        int i=0;        
        int temp = 0;     
        // 将字符数组转换成字节数组,得到字符的字节内容,方便将二进制转为十进制  
        byte content[] = code.getBytes();     
        for (int k = 0; k < content.length; k++) {     
            content[k] = (byte) (content[k] - 48);     
        }  
        //转译后的字节内容数组  
        byte byteContent[];     
        if (content.length % 8 == 0) {// 如果该字符串正好是8的倍数     
            byteContent = new byte[content.length / 8 + 1];     
            byteContent[byteContent.length - 1] = 0;// 那么返回的字节内容数组的最后一个数就补0     
        } else {     
            //否则该数组的最后一个数就是补0的个数  
            byteContent = new byte[content.length / 8 + 2];     
            byteContent[byteContent.length - 1] = (byte) (8 - content.length % 8);     
        }     
        int bytelen=byteContent.length;  
        int contentlen=content.length;  
        // byteContent数组中最后一个是补0的个数,实际操作到次后个元素  
        //Math.pow返回第一个参数的第二个参数次幂的值  
        while (i < bytelen - 2) {     
            for (int j = 0; j < contentlen; j++) {     
                if (content[j] == 1) {// 如果数组content的值为1     
                    s =(int)(s + Math.pow(2, (7 - (j - 8 * i))));// 累积求和     
                }// if     
                if ((j+1)%8==0) {// 当有8个元素时     
                    byteContent[i] = (byte) s;// 就将求出的和放入到byteContent数组中去     
                    i++;     
                    s = 0;// 并重新使s的值赋为0     
                }// if     
            }// for     
        }// while       
        int destlen=dest.length;  
        for(int n=0;n<destlen;n++){     
            temp+=(dest[n]-48)*Math.pow(2, 7-n);//求倒数第2个字节的大小     
        }    
        byteContent[byteContent.length - 2] = (byte) temp;   
        return byteContent;     
    }    
    /** 
     * 压缩并输出新文件 
     * @param content 压缩后产生的新的字节数组 
     * @param fileName 输出文件名 
     */  
    public void CompressFile(byte[] content,String fileName){  
        File file=null;  
        //统一后缀名  
        if(!fileName.endsWith("hfm")){  
            file=new File(fileName+".hfm");  
        }else if(fileName.endsWith("hfm")){  
            file=new File(fileName);      
        }  
        int len=content.length;  
        if(len>0){  
            try{  
                OutputStream out=new FileOutputStream(file);  
                //将字节内容写入文件  
                out.write(content);  
                out.close();  
            }catch(Exception e){  
                e.printStackTrace();  
            }  
        }else{  
            System.out.println("压缩出错");  
        }  
    }  
    /** 
     * 测试一下哈弗曼树建立是否正确 
     * @param hfm 
     */  
    public void PreOrderTraverse(HFMNode hfm){  
        if(hfm!=null){  
            System.out.print(hfm.value+" ");  
            PreOrderTraverse(hfm.lchild);  
            PreOrderTraverse(hfm.rchild);  
        }  
    }  
    /** 
     * 存储哈弗曼树结点的优先队列 
     */  
    ArrayList<HFMNode> nodeList=new ArrayList<HFMNode>();  
    PriorityQueue<HFMNode> nodeQueue=new PriorityQueue<HFMNode>(11,new MyComparator());  
    /** 
     * 实例化的一个比较器类 
     */  
    class MyComparator implements Comparator<HFMNode>{  
        public int compare(HFMNode o1, HFMNode o2) {  
            return o1.value-o2.value;  
        }     
    }  
}