上一篇 赫夫曼编码 详细描述了编码的过程和原理,这里就小小的实践一下。
压缩文件
压缩过程中需要读取待压缩文件,然后读取编码规则(赫夫曼编码表)进而对文件进行编码,完成文件的压缩。
//压缩文件
public static void zipFile(String src,String dst) throws IOException{
//创建一个输入流
InputStream in = new FileInputStream(src);
//创建一个和输入流指向的文件大小一样的byte数组
byte[] b = new byte[in.available()];
//读取文件内容
in.read(b);
in.close();
//使用赫夫曼编码对byte数组进行文件编码
byte[] bytezip = huffmanZip(b);
//文件输出
OutputStream out = new FileOutputStream(dst);
//需要写入编码后的bytezip数组和赫夫曼编码表
ObjectOutputStream oo = new ObjectOutputStream(out);
//写入bytezip数组
oo.writeObject(bytezip);
//写入赫夫曼编码表
oo.writeObject(huffCodes);
oo.close();
out.close();
}
解压文件
文件解压时需要根据文件压缩的编码规则进行压缩。
//解压文件
public static void unzipFile(String src,String dst) throws Exception {
//创建一个输入流
InputStream in = new FileInputStream(src);
ObjectInputStream oin = new ObjectInputStream(in);
//读取bytezip数组
byte[] byteunzip = (byte[]) oin.readObject();
//读取编码规则(赫夫曼编码表)
Map<Byte,String> coderule = (Map<Byte, String>) oin.readObject();
oin.close();
in.close();
//解码
byte[] bytes = decode(coderule,byteunzip);
//创建一个输出流
OutputStream out = new FileOutputStream(dst);
//写出数据
out.write(bytes);
out.close();
}
全部代码
Node类:
public class Node implements Comparable<Node>{
Byte data; //可以为空
int weight;
Node left;
Node right;
public Node(Byte data,int weight) {
this.data = data;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
return o.weight - this.weight; //倒序(从大到小)
}
@Override
public String toString() {
//打印内容
return "Node [data=" + data + ", weight=" + weight + "]";
}
public int getDate() {
return this.weight;
}
}
main:
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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TestHuffmanCode {
public static void main(String[] args) {
// TODO Auto-generated method stub
//文件压缩
// String src = "1.bmp";
// String dst = "2.zip";
// try {
// zipFile(src, dst);
// }catch(IOException e) {
// e.printStackTrace();
// }
//文件解压
String src2 = "2.zip";
String dst2 = "3.bmp";
try {
unzipFile(src2, dst2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//解压文件
public static void unzipFile(String src,String dst) throws Exception {
//创建一个输入流
InputStream in = new FileInputStream(src);
ObjectInputStream oin = new ObjectInputStream(in);
//读取bytezip数组
byte[] byteunzip = (byte[]) oin.readObject();
//读取编码规则(赫夫曼编码表)
Map<Byte,String> coderule = (Map<Byte, String>) oin.readObject();
oin.close();
in.close();
//解码
byte[] bytes = decode(coderule,byteunzip);
//创建一个输出流
OutputStream out = new FileOutputStream(dst);
//写出数据
out.write(bytes);
out.close();
}
//压缩文件
public static void zipFile(String src,String dst) throws IOException{
//创建一个输入流
InputStream in = new FileInputStream(src);
//创建一个和输入流指向的文件大小一样的byte数组
byte[] b = new byte[in.available()];
//读取文件内容
in.read(b);
in.close();
//使用赫夫曼编码对byte数组进行文件编码
byte[] bytezip = huffmanZip(b);
//比较压缩前后的内存消耗
// System.out.println(b.length);
// System.out.println(bytezip.length);
//文件输出
OutputStream out = new FileOutputStream(dst);
//需要写入编码后的bytezip数组和赫夫曼编码表
ObjectOutputStream oo = new ObjectOutputStream(out);
//写入bytezip数组
oo.writeObject(bytezip);
//写入赫夫曼编码表
oo.writeObject(huffCodes);
oo.close();
out.close();
}
//使用指定的赫夫曼编码表进行解码
private static byte[] decode(Map<Byte, String> huffCodes, byte[] bs) {
// TODO Auto-generated method stub
StringBuilder sb = new StringBuilder();
for(int i = 0;i < bs.length;i++) {
byte b = bs[i];
boolean flag = (i == bs.length-1);
sb.append(byteToBitStr(!flag,b));
}
Map<String,Byte> map = new HashMap<>();
for(Map.Entry<Byte, String> entry:huffCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
List<Byte> list = new ArrayList<>();
for(int i = 0;i < sb.length();) {
int count = 1;
boolean flag = true;
Byte b = null;
while(flag) {
String key = sb.substring(i, i+count);
b = map.get(key);
if(b == null) {
count++;
}else {
flag = false;
}
}
list.add(b);
i += count;
}
byte[] bts = new byte[list.size()];
for(int i = 0;i <bts.length;i++) {
bts[i] = list.get(i);
}
return bts;
}
//补齐为全部8位的元素
private static String byteToBitStr(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) {
// TODO Auto-generated method stub
List<Node> nodes = getNodes(bytes);
Node Tree = createHuffmanTree(nodes);
Map<Byte,String> huffCodes = getCodes(Tree);
byte[] c = zip(bytes,huffCodes);
return c;
}
//进行赫夫曼编码
private static byte[] zip(byte[] bytes, Map<Byte, String> huffCodes) {
// TODO Auto-generated method stub
StringBuilder sb = new StringBuilder();
for(byte b:bytes) {
sb.append(huffCodes.get(b));
}
int len;
if(sb.length()%8 == 0) {
len = sb.length()/8;
}else {
len = sb.length()/8 + 1;
}
byte[] by = new byte[len];
int index = 0;
for(int i = 0;i < sb.length();i+=8) {
String StrByte;
if(i+8 > sb.length()) {
StrByte = sb.substring(i);
}else {
StrByte = sb.substring(i, i+8);
}
byte byt = (byte) Integer.parseInt(StrByte,2);
by[index] = byt;
index++;
}
return by;
}
//用于临时存储路径
static StringBuilder sb = new StringBuilder();
//存储赫夫曼编码表
static Map<Byte,String> huffCodes = new HashMap<>();
//根据赫夫曼树获取赫夫曼编码
private static Map<Byte, String> getCodes(Node tree) {
// TODO Auto-generated method stub
if(tree == null) {
return null;
}
getCodes(tree.left,"0",sb);
getCodes(tree.right,"1",sb);
return huffCodes;
}
private static void getCodes(Node node, String code, StringBuilder sb) {
// TODO Auto-generated method stub
StringBuilder sbb = new StringBuilder(sb); //
sbb.append(code);
if(node.data == null) {
getCodes(node.left,"0",sbb);
getCodes(node.right,"1",sbb);
}else {
huffCodes.put(node.data,sbb.toString());
}
}
//创建赫夫曼树
private static Node createHuffmanTree(List<Node> nodes) {
// TODO Auto-generated method stub
while(nodes.size()>1) {
Collections.sort(nodes);
Node left = nodes.get(nodes.size()-1);
Node right = nodes.get(nodes.size()-2);
Node parent = new Node(null,left.getDate() + right.getDate());
parent.left = left;
parent.right = right;
nodes.remove(left);
nodes.remove(right);
nodes.add(parent);
}
return nodes.get(0);
}
//把bytes数组转为Node集合
private static List<Node> getNodes(byte[] bytes) {
// TODO Auto-generated method stub
List<Node> nodes = new ArrayList<>();
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);
}
}
for(Map.Entry<Byte, Integer> entry:counts.entrySet()) {
nodes.add(new Node(entry.getKey(),entry.getValue()));
}
return nodes;
}
}
实验压缩图片
压缩文件结果如下(先压缩后解压):1.BMP 和 3.BMP 图片一样