哈夫曼压缩
做哈夫曼压缩最重要的是先要搞清楚每一步要做什么。。。步骤搞清楚之后才能开始下手
1, 先创建一个大小为256的整形数组,读取一个目标文件,将每个字节做数组下标,字节出现的次数作为数组值,这样就有了一个字节与其次数的对应关系;
2, 接下来再将每个字节的次数作为权值构造哈夫曼树,权值越大的越在下面,同时新建一个节点类,节点类存储字节,和字节的数目,字节编码,之后通过遍历哈夫曼树就能够给每一个字节一个特定的编码了。
3, 在编码完成之后,再读取一次原文件然后每读一个字节就会对应一个01串编码,每次都加起来,这样读取完成之后,就有一个很长的01字符串了,接下来就将01字符串8位8位的截取,每8位01串转成10进制,然后将码表与十进制数组存进文件中,这样就实现了压缩。
4, 而解压就是压缩的逆运算,怎么压的就怎么解压,这个很简单的,呵呵
代码如下:
public class HFMApp {
public ArrayList<Data>Strcodes=new ArrayList<Data>();//存储字节编码的队列
int Btcodes[]=new int[256];//存储字节数目的数组
int[]Intcodes;//存储由二进制转十进制之后的数组
byte addzero;//补0的个数
String Jiestr="";//解压时用到的字符串
/**********************************************************************************************
* 压缩
* ********************************************************************************************/
public void yasuo(String fpath,String npath){//压缩的方法fpath表示原文件路径,npath表示压缩完之后的路径
countBytes(fpath);//得到字节数组
PriorityQueue<TreeNode> queue=array2Queue(Btcodes);//排序,构造哈夫曼树
TreeNode root=queue2HFMTree(queue);//得到根节点
// System.out.println("根节点权值:"+root.nums);
getHFMCodeByTree(root);//得到叶子节点的HFM编码
Intcodes=CutString(Strcodes,fpath);//得到字符串代表的十进制数组
writeYaFile(Intcodes,npath);//写入文件v
System.out.println("压缩完毕");
}
/**
* 1.统计文件中每个字节出现的次数
* @param path
* @return
*/
public void countBytes(String path) {
// 数组的下标作为对应的字节,数组的元素是字节出现的次数
try {
// 建立文件输入流
FileInputStream fis = new FileInputStream(path);
// 包装成数据输入流
BufferedInputStream dis = new BufferedInputStream(fis);
// 读取一个字节
int by = dis.read();
while (by!=-1) {
// System.out.println("字节:"+by);
Btcodes[by]++;
// 读取下一个字节
by = dis.read();
}
// 关闭数据流
fis.close();
} catch (Exception ef) {
ef.printStackTrace();
}
}
/**
* 2.根据字节出现的次数作为权值构造结点对象,放入优先队列
*
* @param counts
* 字节次数的数组
* @return 返回优先队列
*/
public PriorityQueue<TreeNode> array2Queue(int[] counts) {
// 创建优先队列对象
PriorityQueue<TreeNode> queue = new PriorityQueue<TreeNode>(11,
new MyComparator());
// 遍历数组。创建结点对象,放入队列
for (int i = 0; i < counts.length; i++) {
if (counts[i] != 0) {
TreeNode node = new TreeNode(counts[i]);
node.b = (byte) i;// 次数对应的字节
queue.add(node);
}
}
return queue;
}
// 比较器的实现类
private class MyComparator implements Comparator<TreeNode> {
public int compare(TreeNode o1, TreeNode o2) {
return o1.nums - o2.nums;
}
}
/**
* 3.根据优先结点队列构造哈夫曼树
*
* @param queue
* 优先队列
* @return 返回树的根结点
*/
public TreeNode queue2HFMTree(PriorityQueue<TreeNode> queue) {
while (queue.size() > 1) {
// 取出两个权值小的结点
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
// 用两个结点的权值和作为权值创建新结点,作为这个两个结点的父节点
TreeNode root = new TreeNode(node1.nums + node2.nums);
// System.out.println("根节点:"+root.b);
root.left = node1;
root.right = node2;
node1.parent = root;
node2.parent = root;
node1.flag = 0;//左为0
node2.flag = 1;//右为1
// 将新结点加入队列
queue.add(root);//最后一个
}
// 如果队列中只剩下一个结点,即为哈夫曼树的根结点
TreeNode rootNode = queue.poll();
return rootNode;
}
/**
* 根据哈夫曼树得到每个叶子结点的哈夫曼编码
*
* @param root
* @return 将编码放在一个字符串数组中返回,数组长度是256,数组的下标对应每一个字节
*/
public void getHFMCodeByTree(TreeNode root) {
getOneNodeCode(root,"");
}
/**
* 递归得到每个叶结点的编码
*
* @param root 哈夫曼树
* @param codes 存放每个字节编码的字符串数组 数组下标为每个字节
* @param num 生成的哈夫曼编码
*/
public void getOneNodeCode(TreeNode root,String str) {
if (root != null) {
if (root.flag != null) {
str = str + root.flag;
if (root.left == null && root.right == null) {
// 当递归到叶子结点的时候就把得到的字符串放到结点中的字节作为下标的位置
Data d=new Data();//创建一个数据对象
d.bt=root.b;//存入当前字节
d.bm=str;//存入该字节编码
// System.out.println("编码:"+str);
Strcodes.add(d);//将数据对象加入到队列中
}
}
TreeNode left = root.left;
getOneNodeCode(left,str);
TreeNode right = root.right;
getOneNodeCode(right,str);
}
}
/**
* 将字节数组中的01串读取出来,放到一个字符串中
*
* @param codes 存放每个字节编码的字符串数组 数组下标为每个字节
*/
public int[]CutString(ArrayList<Data>Strcodes,String path){
String str="";//创建一个空的字符串
try {
// 建立文件输入流
FileInputStream fis = new FileInputStream(path);
// 包装成数据输入流
DataInputStream dis = new DataInputStream(fis);
// 读取一个字节
while (dis.available()>0) {
byte by = dis.readByte();//读取一个字节
for(int i=0;i<Strcodes.size();i++){
if(Strcodes.get(i).bt==by){
str=str+Strcodes.get(i).bm;
}
}
}
// System.out.println("str___________"+str);
// 关闭数据流
fis.close();
} catch (Exception ef) {
ef.printStackTrace();
}
/*
* 将一整个01字符串8位8位的截取
* */
int len=(str.length()-1)/8+1;//得到数组的长度
int Intcodes[]=new int[len];//创建一个大小为len的数组
for(int i=0;i<Intcodes.length;i++){
if(str.length()>=8){
String somestr="";//创建一个空字符串
somestr=str.substring(0,
;//截取前8位
Intcodes[i]=changeString(somestr);//将前八位转成int型的整数
str=str.substring(8,str.length());//将字符串减小8个字符
// System.out.println("调用了-->"+str);
}else{//如果str的长度小于8
//判断str是否为空
if(str.length()>0){
int size=str.length();
// addzero=(byte)(8-size);//补0数
for(int j=0;j<8-size;j++){//给str补0
str=str+"0";
// System.out.println("调用了》》"+str);
}
Intcodes[i]=changeString(str);//加入数组
return Intcodes;
}
}
}
return Intcodes;
}
/**
* 将二进制转换为十进制
*
*
*/
public int changeString(String s){
return ((int)s.charAt(0)-48)*128+((int)s.charAt(1)-48)*64+((int)s.charAt(2)-48)*32
+((int)s.charAt(3)-48)*16+((int)s.charAt(4)-48)*8+((int)s.charAt(5)-48)*4
+((int)s.charAt(6)-48)*2+((int)s.charAt(7)-48);
}
//写入压缩文件
public void writeYaFile(int Intcodes[], String npath) {
try {
// 创建文件输出流
java.io.FileOutputStream fos = new java.io.FileOutputStream(npath);
java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);
//先存入存放数据的队列长度
if(Strcodes.size()>0)//判断一下大小
dos.write(Strcodes.size());
// System.out.println("存入原长度:"+Strcodes.size());
//之后依次存入队列中每个数据的字节及其所对应的编码
for(int i=0;i<Strcodes.size();i++){
dos.writeByte(Strcodes.get(i).bt);
// System.out.println("存入字节:"+Strcodes.get(i).bt);
dos.writeByte(Strcodes.get(i).bm.length());//存入编码的长度
// System.out.println("存入编码的长度:"+Strcodes.get(i).bm.length());
dos.writeBytes(Strcodes.get(i).bm);//存入编码
// System.out.println("存入编码:"+ Strcodes.get(i).bm);
}
// //然后存入补0的个数
// dos.writeByte(addzero);
// System.out.println("存入补零数:"+addzero);
//遍历数组,一个字节一个字节存入
//然后存入补0的个数
// dos.writeInt(Intcodes.length);//存一下十进制数组的长度
for (int i = 0; i <Intcodes.length; i++) {
dos.write(Intcodes[i]);
// System.out.println("存入十进制:"+Intcodes[i]);
}
// 强制写出
fos.flush();
fos.close();
} catch (Exception ef) {
ef.printStackTrace();
}
}
/**********************************************************************************************
* 解压缩
* ********************************************************************************************/
/*
* 读取压缩文件
* npath指压缩后文件的路径
* epath指解压后文件的路径
* */
public void JieYa(String npath,String epath){
try {
FileInputStream fis=new FileInputStream(npath);//创建文件输入流
DataInputStream dis=new DataInputStream(fis);//创建数据输入流
//读出数据队列的长度
int length=dis.readByte();
if(length>=-128&&length<=0)
length+=256;//都加上256
// System.out.println("读出数据队列的长度:"+length);
for(int i=0;i<length;i++){//依次读入数据队列中的数据
Data data=new Data();//创建一个数据对象
data.bt=dis.readByte();
byte size=dis.readByte();//读取编码的长度
byte Bt[]=new byte[size];
dis.read(Bt);
data.bm=new String(Bt);//写入编码
Strcodes.add(data);//加入数据队列
// System.out.println("读取字节:"+ data.bt);
// System.out.println("读取编码的长度:"+size);
// System.out.println("读取编码:"+ data.bm);
}
// addzero=dis.readByte();//读取补零数
// System.out.println("读取补零数:"+addzero);
int asize=dis.available();//读取剩余字节数
// System.out.println("读取十进制数组的大小:"+asize);
int codes[]=new int[asize];
for(int i=0;i<asize;i++){//依次读取十进制数
codes[i]=dis.readByte();
if(codes[i]>=-128&&codes[i]<0)
codes[i]+=256;//都加上256
Jiestr+=changeInteger(codes[i]);
// System.out.println("读取十进制数:"+codes[i]);
}
// Jiestr=Jiestr.substring(0, Jiestr.length());//减去补0数
// System.out.println("解压后的字符串:"+Jiestr);
}
catch (Exception ef) {
ef.printStackTrace();
}
try{// 创建文件输出流
java.io.FileOutputStream fos = new java.io.FileOutputStream(epath);
java.io.DataOutputStream dos = new java.io.DataOutputStream(fos);
boolean bool=true;
while(bool){
bool=false;
for(int i=0;i<Strcodes.size();i++){
if(Jiestr.startsWith(Strcodes.get(i).bm)){
dos.writeByte(Strcodes.get(i).bt);//写入文件
Jiestr=Jiestr.substring(Strcodes.get(i).bm.length());//截取字符串
bool=true;
break;
}
}
}
System.out.println("解压完毕");
} catch (Exception ef) {
// TODO Auto-generated catch block
ef.printStackTrace();
}
}
/*
* 将十进制转换为二进制
*
* */
public String changeInteger(int num){
String str="";//创建一个star用来转换01串
String star="";//创建一个star用来转换01串
while(num!=0){
if(num%2==0){
str=str+"0";
num=num/2;
}
if(num%2==1){
str+="1";
num=num/2;//num除以2
}
}
int size=8-str.length();
for(int i=0;i<size;i++){
str=str+"0";
}
// System.out.println("str长度:"+str.length());
// System.out.println("str:"+str);
str+="0";
for(int i=str.length()-2;i>=0;i--){
star+=str.substring(i,i+1);
}
// System.out.println("star:"+star);
return star;
}
}