哈夫曼压缩:
1、读取文件,并统计文件中各字符出现的次数,存储到一个数组中,数组长度为256,然后每个索引都代表字符的AscII码所代表的值,每当多一个,就在这个索引下的值加一。代码如下:
public void Output() {
try {
// 实例化一个File输入流对象
InputStream is = new FileInputStream(path);
// 实例化一个Buffer输入流对象
BufferedInputStream bs = new BufferedInputStream(is);
// 获得文件的长度
int length = is.available();
// 遍历
for (int i = 0; i < length; i++) {
// 统计每个字符出现的次数
array[is.read()]++;
}
// 关闭流
bs.close();
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2、对这个数组里的值进行排序(采用优先队列)
①循环创建节点,节点里存放AscII码和此AscII出现的次数
②优先队列里存放节点
③优先队列的排序方法通过继承Comparable<T>接口来实现
代码如下:
public void CreatThree() {
// 定义一个计数器,记录有多少个叶子节点
int count = 0;
// 实例化一个优先队列
PriorityQueue<hfmNode> node_queue = new PriorityQueue<hfmNode>();
// 遍历数组,往优先队列里添加内容
for (int i = 0; i < array.length; i++) {
if (array[i] != 0) {
hfmNode node = new hfmNode(i, array[i]);
node_queue.add(node);
count++;
}
}
3、构建一个哈夫曼树,并获取每个字符对应的码表
①首先从优先队列里获得2个最少出现的字符,并从队列中移除这两个
②然后构建一个小树,生成一个父节点,父节点的次数为这两个字符次数的合,并把这个父节点添加到优先队列里
③以此类推构建完哈夫曼树
④每一个父节点的左边路径作为0,右边路径作为1,然后生成每个字符所对应的码表
代码如下
// 构建哈夫曼树
while (node_queue.size() > 1) {
// 获得队列头,并移除这个队列头
hfmNode node1 = node_queue.poll();
// 获得队列头,并移除这个队列头
hfmNode node2 = node_queue.poll();
// 创建一个上面两个节点的父节点
hfmNode parent_node = new hfmNode(0, node1.getCount()
+ node2.getCount());
// 设置父节点的左右节点
parent_node.setLeftChild(node1);
node1.setS("0");
parent_node.setRightChild(node2);
node2.setS("1");
// 把这个节点添加进优先队列里
node_queue.add(parent_node);
}
// 设置根节点
root = node_queue.peek();
}
/**
* 给每个叶子节点添加码表值
*
* @param node
*/
public void PrintLn(hfmNode node) {
if (node.getLeftChild() == null && node.getRightChild() == null) {
// 把叶子节点添加到节点数组里
node_all[co] = node;
co++;
} else {
// 获取当前节点的编码值
String s = node.getS();
// 左节点的编码值加上当前节点的编码值
s += node.getLeftChild().getS();
// 把加后的编码值赋值给左节点
node.getLeftChild().setS(s);
// 递归左节点
PrintLn(node.getLeftChild());
// 获取当前节点的编码值
String s1 = node.getS();
// 右节点的编码值加上当前节点的编码值
s1 += node.getRightChild().getS();
// 把加后的编码值赋值给右节点
node.getRightChild().setS(s1);
// 递归右节点
PrintLn(node.getRightChild());
}
}
4、写入文件
①写入头信息
I、写入字符
II、写入该字符编码所占的字节数
Ⅲ、每个字符对应的码表补了多少个0(补满8位)
Ⅳ、写入每个字符所对应的码表
for (int i = 0; i < node_all.length; i++) {
//记录该节点编码有多少个字节
count_array = node_all[i].getByteSize();
// 写入该字符占有几个位置
bos.write(node_all[i].getByteSize());
// 写入字符
bos.write(node_all[i].getObj());
// 写入补了多少个0
String str_zero = Integer.toString(node_all[i]
.getAddZero());
bos.write((int) str_zero.charAt(0));
//获得当前字符的编码
writes = node_all[i].getS();
//循环写入编码
for (int j = 0; j < count_array; j++) {
//定义一个中转字符串
String trans = "";
//提取前八位
for (int k = 0; k < 8; k++) {
trans += writes.charAt(k);
}
//转换成整型写入文件
int x = ChangeString(trans);
// 写入转换后的整型
bos.write(x);
// 中转字符串清空
trans = "";
// 循环获得除去前八位后剩下的字符
for (int k = 8; k < writes.length(); k++) {
trans += writes.charAt(k);
}
// 把原先的字符串覆盖掉
writes = trans;
// 中转字符串清空
trans = "";
}
}
②写入文件内容
Ⅰ、把所有的字符转化成01串,每8位一个字节写入文件
Ⅱ、最后不足八位则补零,然后写入补零个数
// 遍历读取文件内容,并写入文件
for (int i = 0; i < length; i++) {
// 读取文件
int x = bs.read();
// 循环判断该字符所对应的编码
for (int j = 0; j < str_all.length; j++) {
if (x == node_all[j].getObj()) {
// 把编码赋值给字符串中存储
str_matter += str_all[j];
}
}
// 如果此时的字符串位数大于等于8
if (str_matter.length() >= 8) {
// 循环获得前八位
for (int j = 0; j < 8; j++) {
str_trans += str_matter.charAt(j);
}
// 把这8位字符串转化成整形
int a = ChangeString(str_trans);
// 写入转换后的整型
bos.write(a);
// 清空转换字符串
str_trans = "";
// 循环获得除去前八位后剩下的字符
for (int j = 8; j < str_matter.length(); j++) {
str_trans += str_matter.charAt(j);
}
// 把原先的字符串覆盖掉
str_matter = str_trans;
// 清空转换字符串
str_trans = "";
}
}
// 如果没有满八位,则补上0
while (str_matter.length() % 8 != 0) {
addzero++;
str_matter += "0";
}
// 判断补零后的字符串是否为空
if (str_matter.equals("")) {
} // 把这8位字符串转化成整形
else {
int b = ChangeString(str_matter);
// 写入转换后的整型
bos.write(b);
}
// 写入补零计数器
String so = Integer.toString(addzero);
bos.write((int) so.charAt(0));