数据压缩实践-用哈夫曼编码压缩解压文件
引言:
哈夫曼编码的一大用处便是数据压缩,我们在学完之后,当然要学会试着运用其来压缩解压文件。一下便是写着做的文件压缩尝试。
文件准备:
首先在D盘下准备个了个34kB大小的数据库脚本文件
我们用记事本将其打开,内容如下:
代码实现:
接着我们在前几篇写的哈夫曼编码实现代码中加入对文件的解压和压缩方法(zipFile(srcFile,dstFile)和upZipFile(srcFile,dstFile).
参考代码如下:
//编写方法,对文件进行压缩
/**
*
* @param srcFile传入的要压缩文件的全路径
* @param dstFile 压缩后压缩文件的存放位置
*/
public static void zipFile(String srcFile,String dstFile) {
//创建输出流
OutputStream os=null;
FileInputStream is=null;
ObjectOutputStream oos=null;
try {
//创建文件输入流
is=new FileInputStream(srcFile);
//创建一个和源文件大小一样的byte[]
byte[] b=new byte[is.available()];
//读取文件
is.read(b);
//获取文件对应的哈夫曼编码表
byte[] huffmanBytes=huffmanZip(b);
//创建文件的输出流:
os=new FileOutputStream(dstFile);
//创建一个文件输出流相关联的Object流,可直接输出对象
oos=new ObjectOutputStream(os);
//把哈夫曼编码出的字节数组写入到压缩文件
oos.writeObject(huffmanBytes);
//把哈夫曼编码写入压缩文件
//这里以对象流的方式写出哈夫曼编码,为了恢复文件方便
oos.writeObject(huffmanCodes);
}catch(Exception e) {
System.out.println(e.getMessage());
}finally {
try {
is.close();
os.close();
oos.close();
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
解压方法:
//编写方法,对压缩文件解压
/**
*
* @param zipFile 准备解压的文件
* @param dstFile 将文件解压到哪个路径
*/
public static void unZipFile(String zipFile,String dstFile) {
//定义文件输入流
InputStream is=null;
//定义对象输入流
ObjectInputStream ois=null;
//定义文件的输出流
OutputStream os=null;
try {
//创建文件输入流
is=new FileInputStream(zipFile);
//创建对象输入流
ois=new ObjectInputStream(is);
//读取byte数组,huffmanBytes
byte[] huffmanBytes=(byte[])ois.readObject();
//读取哈夫曼编码表
Map<Byte,String> codes=(Map<Byte, String>) ois.readObject();
//解码
byte[] bytes= decode(codes,huffmanBytes);
//将bytes数组写入到目标文件
os=new FileOutputStream(dstFile);
//写出数据到文件中
os.write(bytes);
}catch(Exception e) {
System.out.println(e.getMessage());
}finally {
try {
os.close();
ois.close();
is.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在对文件进行压缩后得到jsys.zip
可以看到得到了28KB的压缩文件
再对文件进行解压后得到jtsys.sql
可看到大小和原文件大小一样。
打开其内容:
没有变化。
全部代码参考:
package Tree06;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HuffmanCode {
public static void main(String[] args) {
String srcFile="D://jtsys.sql";
String dstFile="D://jtsys.zip";
zipFile(srcFile,dstFile);
System.out.println("压缩~~~~~ok");
String srcFile2="D://jtsys.zip";
String dstFile2="D://jtsys2.sql";
unZipFile(srcFile2,dstFile2);
}
//编写方法,对压缩文件解压
/**
*
* @param zipFile 准备解压的文件
* @param dstFile 将文件解压到哪个路径
*/
public static void unZipFile(String zipFile,String dstFile) {
//定义文件输入流
InputStream is=null;
//定义对象输入流
ObjectInputStream ois=null;
//定义文件的输出流
OutputStream os=null;
try {
//创建文件输入流
is=new FileInputStream(zipFile);
//创建对象输入流
ois=new ObjectInputStream(is);
//读取byte数组,huffmanBytes
byte[] huffmanBytes=(byte[])ois.readObject();
//读取哈夫曼编码表
Map<Byte,String> codes=(Map<Byte, String>) ois.readObject();
//解码
byte[] bytes= decode(codes,huffmanBytes);
//将bytes数组写入到目标文件
os=new FileOutputStream(dstFile);
//写出数据到文件中
os.write(bytes);
}catch(Exception e) {
System.out.println(e.getMessage());
}finally {
try {
os.close();
ois.close();
is.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//编写方法,对文件进行压缩
/**
*
* @param srcFile传入的要压缩文件的全路径
* @param dstFile 压缩后压缩文件的存放位置
*/
public static void zipFile(String srcFile,String dstFile) {
//创建输出流
OutputStream os=null;
FileInputStream is=null;
ObjectOutputStream oos=null;
try {
//创建文件输入流
is=new FileInputStream(srcFile);
//创建一个和源文件大小一样的byte[]
byte[] b=new byte[is.available()];
//读取文件
is.read(b);
//获取文件对应的哈夫曼编码表
byte[] huffmanBytes=huffmanZip(b);
//创建文件的输出流:
os=new FileOutputStream(dstFile);
//创建一个文件输出流相关联的Object流,可直接输出对象
oos=new ObjectOutputStream(os);
//把哈夫曼编码出的字节数组写入到压缩文件
oos.writeObject(huffmanBytes);
//把哈夫曼编码写入压缩文件
//这里以对象流的方式写出哈夫曼编码,为了恢复文件方便
oos.writeObject(huffmanCodes);
}catch(Exception e) {
System.out.println(e.getMessage());
}finally {
try {
is.close();
os.close();
oos.close();
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
/***
*
* @param huffmanCodes 哈夫曼编码表 map
* @param huffmanByte 哈夫曼编码得到的字节数组
* @return 返回原来的字符串对应的字符数组
*/
private static byte[] decode(Map<Byte,String> huffmanCodes,byte[] huffmanByte) {
//1.先得到huffmanBytes对应的二进制的字符串,形式1010100010111.。
StringBuilder stringBuilder=new StringBuilder();
//将byte数组转换成二进制的字符串
for(int i=0;i<huffmanByte.length;i++) {
//判断是否是最后一个字节(最后一个字节不用补高位
boolean flag=(i==huffmanByte.length-1);
stringBuilder.append(byteToBitString(!flag,huffmanByte[i]));
}
//把字符串安装指定的哈夫曼编码进行解码
//把哈夫曼编码表进行调换,反向查询a->100,100->a
Map<String,Byte> map=new HashMap<String,Byte>();
for(Map.Entry<Byte, String>entry:huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
//创建一个集合,存放byte
List<Byte> list=new ArrayList<>();
for(int i=0;i<stringBuilder.length();) {
int count=1;
boolean flag=true;
Byte b=null;
while(flag) {
//1010100010111...
//递增取出key1
String key=stringBuilder.substring(i,i+count);//i不动,让count移动, 指定匹配下一个字符
b=map.get(key);
if(b==null) {
//说明没有匹配到
count++;
}else {
flag=false;
}
}
list.add(b);
i+=count;
}
//当for循环结束后,我们list循环中就存放了所有的字符
//把list中的所有数据放入到byte[],并返回
byte[] b=new byte[list.size()];
for(int i=0;i<b.length;i++) {
b[i]=list.get(i);
}
return b;
}
/***
* 将一个byte转成一个二进制的字符串。
* @param flag 标志是否需要补高位,如果是true,表示补高位,反之不补
* @param b 传入的byte
* @return 是该byte对应的二进制的字符串
*/
static String byteToBitString(boolean flag,byte b) {
int temp=b;
//如果是正数我们还存在补高位
if(flag) {
temp |=256;
}
String str=Integer.toBinaryString(temp);
if(flag) {
return str.substring(str.length()-8);
}else {
return str;
}
}
/***
* 生成哈夫曼编码的总方法
*
*/
private static byte[] huffmanZip(byte[] bytes) {
//利用传进来的bytes构建哈夫曼树的节点
List<Node> nodes=getNodes(bytes);
//利用构建的节点生成哈夫曼树,返回该树的根节点
Node HuffmanRoot=createHuffmanTree(nodes);
//利用getCodes方法获取每个字符对应的哈夫曼编码,存入到huffmanCodes编码表中
getCodes(HuffmanRoot);
for(Map.Entry<Byte,String> entry:huffmanCodes.entrySet()) {
System.out.println(entry.getKey()+"->"+entry.getValue());
}
//利用得到的huffman编码表将对应的字符串转换为字节码数组
byte[] zipHuff = zipHuff(bytes,huffmanCodes);
return zipHuff;
}
/**
* 将字符串对应的byte[]数组,通过生成的哈夫曼编码表,生成哈夫曼编码
* @param bytes 原始的字符串对应的byte[](contentBytes)
* @param huffmanCodes生成的哈夫曼编码表
* @return 返回字符串对应的哈夫曼编码
* 此方法的目的是将目的字符串转换成对应的哈夫曼编码后,每八位一个字节装入byte数组里去
*/
public static byte[] zipHuff(byte[] bytes,Map<Byte,String>huffmanCodes) {
//1.利用huffmanCodes将bytes转换成对应的字符串
StringBuilder stringBuilder=new StringBuilder();
for(byte b:bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
//统计返回byte[] huffmanCodeBytes长度
int len;
if(stringBuilder.length()%8==0) {
len=stringBuilder.length()/8;
}else {
len=stringBuilder.length()/8+1;
}
byte[] huffmanCodeBytes=new byte[len];
int index=0;
for(int i=0;i<stringBuilder.length();i+=8) {
String strByte;
if(i+8>stringBuilder.length()) {
strByte=stringBuilder.substring(i);
}else {
strByte=stringBuilder.substring(i,i+8);
}
//将strByte转成一个byte,放入到huffmanCodeBytes
huffmanCodeBytes[index]=(byte)Integer.parseInt(strByte, 2);
index++;
}
return huffmanCodeBytes;
}
//生成哈夫曼编码:
/**
* 1.将哈夫曼编码表存放在Map<Byte,String>形式:字符的ascii码->对应的哈夫曼编码
* 2.拼接路径,利用StringBuilder存储某个叶子节点的路径
* @param root
*/
static Map<Byte,String> huffmanCodes=new HashMap<Byte,String>();
static StringBuilder stringBuilder=new StringBuilder();
//重载getCodes
private static Map<Byte,String> getCodes(Node root){
if(root==null) {
return null;
}
//处理root的左子树
getCodes(root.left,"0",stringBuilder);
//处理右子树
getCodes(root.right,"1",stringBuilder);
return huffmanCodes;
}
//生成传入的node节点的所有叶子节点的哈夫曼编码,并放入集合,code:路径
private static void getCodes(Node node,String code,StringBuilder stringBuilder) {
StringBuilder stringBuilder2=new StringBuilder(stringBuilder);
stringBuilder2.append(code);
if(node!=null) {
if(node.data==null) {
//递归处理
getCodes(node.left,"0",stringBuilder2);
getCodes(node.right,"1",stringBuilder2);
}else {
//说明是一个叶子节点
//表示找到某个叶子节点
huffmanCodes.put(node.data, stringBuilder2.toString());
}
}
}
//前序遍历
public static void frontShow(Node root) {
if(root!=null) {
root.frontShow();
}else {
System.out.println("树为空");
}
}
private static List<Node> getNodes(byte[] bytes){
//创建List
ArrayList<Node> nodes=new ArrayList<Node>();
//得到:"i like like like java do you like a java"对应的byte[]数组
Map<Byte,Integer> counts=new HashMap<>();
for(byte b:bytes) {
Integer count=counts.get(b);
if(count==null) {
counts.put(b,1);
}else {
counts.put(b, count+1);
}
}
//把每一个键值对转成一个Node对象,并加入到nodes集合
for(Map.Entry<Byte,Integer> entry:counts.entrySet()) {
nodes.add(new Node(entry.getKey(),entry.getValue()));
}
return nodes;
}
//通过List创建对应的哈夫曼树
private static Node createHuffmanTree(List<Node> nodes) {
while(nodes.size()>1) {
Collections.sort(nodes);
//取出第一颗最小的二叉树
Node leftNode=nodes.get(0);
Node rightNode = nodes.get(1);
//创建一棵新的二叉树(没有data,只有权值)
Node parent=new Node(null,leftNode.weight+rightNode.weight);
parent.left=leftNode;
parent.right=rightNode;
//将已经处理两颗二叉树移除
nodes.remove(leftNode);
nodes.remove(rightNode);
//加入新的
nodes.add(parent);
}
return nodes.get(0);
}
}
//创建Node
class Node implements Comparable<Node>{
Byte data;//存放数据:字符对应的ascii码值
int weight;//权值,字符出现的次数
Node left;
Node right;
public Node(Byte data, int weight) {
super();
this.data = data;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
// TODO Auto-generated method stub
//升序
return this.weight-o.weight;
}
@Override
public String toString() {
return "Node [data=" + data + ", weight=" + weight + "]";
}
//前序遍历
public void frontShow() {
System.out.println(this);
if(this.left!=null) {
this.left.frontShow();
}
if(this.right!=null) {
this.right.frontShow();
}
}
}