在(一)中讲解说明了文本字符串转换为哈希表(字符串:哈夫曼编码)的过程。接下来主要讲解文件读入(即把文件内容转换为字符串的过程)和将压缩后的文件输出。
一 读文件
首先,我们要将指定路径的文件读入,然后将文本内容转换为字符串,再传给statistic方法。接下来就创建readFile方法来读文件获取要压缩的内容,public String readFile(String fileName){...}。创建StringBuffer新对象buffer,这里这个buffer可以认为是一个可变的字符串对象,对它进行操作的时候不会产生新的对象,StringBuffer buffer = new StringBuffer();然后创建数据输入流DataInputStream新对象DataInputStream in = null;(先创建一个空对象的原因是为了把后面用构造方法创建数据输入流对象的过程放入try...catch中),创建数据输入流DataInputStream新对象的构造方法的参数数据类型是FileInputStream文件字节输入流,可以对文件数据以字节的形式进行读取操作,然后创建FileInputStream对象构造方法需要的参数数据类型为File对象,所以创建数据输入流DataInputStream新对象的代码依次为File file = new File(fileName);FileInputStream fileinputstream = new FileInputStream(file); in = new DataInputStream(fileinputstream);,然后定义一个字符串变量temp保存文件每一行,然后就开始循环遍历开始逐行读取文件每一行,把读到的内容赋值给temp变量,然后把temp加入到buffer中即可,如果为空则说明到了文件结尾了,此时停止,代码为while((temp = in.readLine())!=null){buffer.append(temp);}。创建新数据输入流对象这个过程容易发生异常,所以这里为了处理异常使用try...catch方法,把创建过程和读取放在try块中,然后catch中加入e.printStackTrace();最后再加入finally语句,在其中关闭字节流,in.close();并且可以对in.close()再进行一次try catch嵌套。此部分代码如下
//将文件中的内容转换为字符串,读入文件方法,把文件读进来
public String readFile(String filename){
StringBuffer buffer = new StringBuffer();
DataInputStream in = null;
try {
File file =new File(filename);
FileInputStream fileinputstream = new FileInputStream(file);
in = new DataInputStream(fileinputstream);
String temp = "";
while((temp=in.readLine())!=null){
buffer.append(temp+"\n");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer.toString();
}
二 输出文件
输出文件首先要知道输出的东西都包含什么,首先我们要明确输出的内容包括两个部分:输出码表和输出源内容的每个字符。输出码表指的就是字符及对应的二进制编码,比如a对应01,b对应001等,基于输出码表我们能够知道字符对应的二进制编码是什么(解码时候要用)。输出方法为outData,参数包括输入的文本内容的字符串str、第三步中生成的字符哈夫曼编码的哈希表码表map 以及输出文件的文件名outFileName。然后开始写方法体。
首先是创建数据输出流对象os,加上处理异常的try catch 作为outData方法的框架。然后输出码表,设置一个方法outCodes。我们输出原内容的每个字符对应的huffman编码,也加设置一个方法sourceHuffmanStr。
对于方法outCodes,我们要用到数据输出流os,因为需要它来向指定文件进行输出,然后输出的是码表,所以也把码表map传进来。
然后我们要把原内容转换为哈夫曼编码的串,这里用到两个方法。 首先定义方法source2HuffmanStr,参数为传入的输入的文本内容的字符串str、字符哈夫曼编码的哈希表码表map,通过这个source2HuffmanStr方法我们可以得到输入的文本内容的字符串对应的哈夫曼编码串(二进制01串)dataHuffmanCode。其次定义方法outDataHuffmanCode,方法参数为文本内容字符对应的哈夫曼编码串dataHuffmanCode 以及数据输出流os,这个方法可以将得到的哈夫曼编码串进行输出。
此时我们就大体得到了outData方法的大体框架。先给出如下的框架代码部分。
//输出方法
public void outData(String str,Map<String,String> map,String outFileName){
//创建空输出信息流
DataOutputStream os = null;
try {
os = new DataOutputStream(new FileOutputStream(new File(outFileName)));
//1 输出码表
this.outCodes(os,map);
//2 输出元文本内容对应的哈夫曼字符串
String dataHuffmanCode = this.source2HuffmanStr(str,map);
this.outDataHuffmanCode(os,dataHuffmanCode);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//输出码表的方法outCodes(输出字符对应的哈夫曼编码的信息,在解码时也应用的到),只要把它输出出去,就要用到输出信息流os,显然还要加上码表map)
public void outCodes(DataOutputStream os,Map<String,String>map){
}
//得到文本内容对应的哈夫曼编码串的方法source2HuffmanStr
public String source2HuffmanStr(String str,Map<String,String> map){
String dataHuffmanCode="";
return dataHuffmanCode;
}
//将得到的哈夫曼编码串输出,使用方法outDataHuffmanCode
public void outDataHuffmanCode(DataOutputStream os,String dataHuffmanCode){
}
接着就一个一个的来充实子方法。首先是outcodes方法,给一个码表,要输出的东西首先是码的个数,即这里代表的是一共有多少种字符(在解压的时候也可以根据码的个数还原成map或数组),然后对于每个单个字符,我们要输出每个 字符的本身、字符的编码长度是两位还是三位以及具体的哈夫曼编码也要输出一下。其中用到一些方法,writechar方法是将字符写入数据输出流(并输出值指定文件),writechars是将字符串写入数据输出流,writeInt是将int型数据写入数据输出流。附上方法最后的代码
//输出码表的方法outCodes(输出字符对应的哈夫曼编码的信息,在解码时也应用的到),只要把它输出出去,就要用到输出信息流os,显然还要加上码表map)
public void outCodes(DataOutputStream os,Map<String,String>map) throws IOException {
//1 输出字符的个数
os.writeInt(map.size());
for (String c:map.keySet()){ //注:map的键的数据类型是String
//2.输出字符本身 以及哈夫曼编码的长度
os.writeChar(c.charAt(0));
os.writeInt(map.get(c).length());
//3.输出字符的哈夫曼编码
os.writeChars(map.get(c));
}
}
然后是要把原始的内容转换为huffman编码串,即source2HuffmanStr方法。 这个方法比较简单,就创建可变字符串对象类buffer然后使用循环,不断扩充buffer即可,最后再将buffer返回。附上方法最后的代码
//得到文本内容对应的哈夫曼编码串的方法source2HuffmanStr
public String source2HuffmanStr(String str,Map<String,String> map){
StringBuffer buffer = new StringBuffer();
char[] cs = str.toCharArray();
for (char c:cs){
buffer.append(map.get(""+c));//map的键的数据类型为String,所以加上“”
}
return buffer.toString();
}
最后我们把哈夫曼编码串返回,即输出哈夫曼编码串(重要),输出原始内容转换成为的huffman编码串,即方法outDataHuffmanCode。这里我们哈夫曼编码串是字符串,(应压缩文件的要求)我们要把它们转换成二进制数组,这里就要定义子方法string2ByteArrays。然后方法体outDataHuffmanCode的过程分为三步:1.要把这个Huffman编码串转换成为对应的byte[] 2.输出byte数组个数 3.输出byte数组。outDataHuffmanCode方法代码如下
//将得到的哈夫曼编码串输出,使用方法outDataHuffmanCode
public void outDataHuffmanCode(DataOutputStream os,String dataHuffmanCode){
//1.把这个哈夫曼编码串转换成为对应的byte[]
byte[] bt = this.string2ByteArrays(dataHuffmanCode);
try {
//2.输出byte数组个数
os.writeInt(bt.length);
//3.输出byte数组
os.write(bt);
} catch (IOException e) {
e.printStackTrace();
}
}
然后主要看一下方法outDataHuffmanCode依托的string2ByteArrays方法。这个方法分三部分。1.判断整个串的长度是否能被8整除。 2.能整除的话,就8位作为一个byte。3.不能整除的话,最后一个字符串,后面补0,使其成为8位,作为一个byte,同时要记录补0的个数(这是为了在后续解码的时候方便)。首先创建字节数组,代码为byte[] retBytes = null;,然后把字符串哈夫曼编码串转换为字符数组, char[] cs=dataHuffmanCode.toCharArray();然后得到字符数组的长度len,以及定义新创建的字节数组的长度lenByte=len+1,这里加1位是用来存放补零的个数。然后即开始判定整个串的长度能否被8整除,这里用if条件语句来进行判定。
然后这里用到了一个把字符转换为字节的方法,定义方法char2byte,这个方法把一个8位字节的每一位的具体值都计算出来,最后会进行相加得到一个8位的字节值。public byte char2byte(String s){...},创建byte ret来不断循环得到最后值,注意要有一个强制转型的步骤(因为字节和字节相加java会自动变成int数据类型),然后调用Byte.parseByte()方法将字符串转换为数值,然后进行2的指定次幂的运算,最后保留了真值。附上char2byte的代码
//把一个字符串转换为byte
public byte char2byte(String s){
char[] cs = s.toCharArray();
byte ret = 0;
for (int i = 0;i<cs.length;i++){
//计算每一位char代表的真正的数值
byte tempB = (byte)(Byte.parseByte(""+cs[i])*Math.pow(2, cs.length)-i-1);
ret = (byte) (ret+tempB);
}
return ret;
}
然后做好了char2byte的代码后,就接着充实string2ByteArray方法。
//将得到的二进制编码串转换为真正的二进制数组
public byte[] string2ByteArrays(String dataHuffmanCode){
byte[] retBytes = null;
char[] cs = dataHuffmanCode.toCharArray();
int len =cs.length;
int lenByte = 0;
//1.判断整个串的长度能否被8整除
if (len%8==0){
//2.能整除的话,就8位作为一个byte
lenByte=len+1;
retBytes = new byte[lenByte];
for (int i =0;i<lenByte-1;i++){
String s = "";
for (int j =i*8;j<(i+1)*8;j++){
s+=cs[j];
}
retBytes[i]=char2byte(s);//s是作为参数
}
// retBytes最后一位也要加上0
retBytes[lenByte-1]=0;
}
//3.不能整除的话,那么就最后一个字符串后面补0
else{
//3 如果不能整除的话,就补0
lenByte=len/8+2;
retBytes = new byte[lenByte];
int bl = 8-len%8;
for (int k=0;k<bl;k++){
dataHuffmanCode+="0";
}
cs = dataHuffmanCode.toCharArray();
for (int i =0;i<lenByte-1;i++){
String s= "";
for (int j=i*8;j<(i+1)*8;j++){
s+=cs[j];
}
retBytes[i] = char2byte(s);
}
retBytes[lenByte-1]=(byte)bl;
}
return retBytes;
}
最后把HuffmanTree类的所有代码附上,这就完成了使用哈夫曼树进行压缩文件的所有代码。
package com.ms.compressfiles0801;
import javafx.util.converter.ByteStringConverter;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
//使用哈夫曼算法实现压缩
public class HuffmanTree {
//压缩方法,这个方法就是把内容传进来,对数据进行压缩,输入字符串类型数据,方法输出仍然为字符串类型
public String compress(String str){
//1 统计:读入要压缩的源文件,统计字符出现的次数,并构造成优先级队列
HuffmanPriorityQueue queue = this.statistics(str);
//2 建树:构建哈夫曼树
HuffmanNode tree = this.buildHuffmanTree(queue);
//3 编码:对哈夫曼树左边记0,右边记1,就可以得到字符的哈夫曼编码
Map<String,String> map =new HashMap<>(); //这个哈希表是用来存放字符及其结果的地方
this.buildHuffmanCode(map,tree," ");
//4 输出:将文件中的字符的逐步进行输出
return "";
}
//统计步骤中使用到了statistics方法,将传入的字符串操作的到了一个哈夫曼优先级队列,队列字符按照权值大小排列
public HuffmanPriorityQueue statistics(String str){
//1 统计次数:
Map<Character,Integer> mapp = new HashMap<Character, Integer>();
char[] cs = str.toCharArray();
for(char c : cs ){
Integer obj = mapp.get(c);
if (obj==null){
mapp.put(c,1);
}else{
mapp.put(c, obj+1);不同点
}
}
//2.构建优先级队列
HuffmanPriorityQueue queue = new HuffmanPriorityQueue(mapp.size());
for (char c:mapp.keySet()){
HuffmanNode node = new HuffmanNode(c,mapp.get(c));
queue.insert(node);
}
return queue;
}
//建树过程中使用到了buildHuffmanTree方法,将优先级队列转换为哈夫曼树。
public HuffmanNode buildHuffmanTree(HuffmanPriorityQueue que){
while(que.size()>1){
//先取两个最小权重的对象
HuffmanNode n1 = que.remove();
HuffmanNode n2 = que.remove();
//构建这两个对象的父对象
HuffmanNode n = new HuffmanNode('k', n1.getCount()+ n2.getCount());
n.setLeftChild(n1);
n.setRightChild(n2);
que.insert(n);
}
return que.peekfront();
}
//编码过程中使用到buildHuffmanCode方法
public void buildHuffmanCode(Map<String,String> map,HuffmanNode tree,String zoro){
if ((tree.getLeftChild()==null)&&(tree.getRightChild()==null)){
map.put(""+tree.getC(),zoro);//这就是单个根节点和叶子节点的特征
}
if (tree.getLeftChild()!=null){
this.buildHuffmanCode(map,tree.getLeftChild(),zoro+"0");
}
if (tree.getRightChild()!=null){
this.buildHuffmanCode(map,tree.getRightChild(),zoro+"1");
}
}
//将文件中的内容转换为字符串,读入文件方法,把文件读进来
public String readFile(String filename){
StringBuffer buffer = new StringBuffer();
DataInputStream in = null;
try {
File file =new File(filename);
FileInputStream fileinputstream = new FileInputStream(file);
in = new DataInputStream(fileinputstream);
String temp = "";
while((temp=in.readLine())!=null){
buffer.append(temp+"\n");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return buffer.toString();
}
//输出方法
public void outData(String str,Map<String,String> map,String outFileName) throws IOException {
//创建空输出信息流
DataOutputStream os = null;
try {
os = new DataOutputStream(new FileOutputStream(new File(outFileName)));
//1 输出码表
this.outCodes(os,map);
//2 输出元文本内容对应的哈夫曼字符串
String dataHuffmanCode = this.source2HuffmanStr(str,map);
this.outDataHuffmanCode(os,dataHuffmanCode);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally{
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//输出码表的方法outCodes(输出字符对应的哈夫曼编码的信息,在解码时也应用的到),只要把它输出出去,就要用到输出信息流os,显然还要加上码表map)
public void outCodes(DataOutputStream os,Map<String,String>map) throws IOException {
//1 输出字符的个数
os.writeInt(map.size());
for (String c:map.keySet()){ //注:map的键的数据类型是String
//2.输出字符本身 以及哈夫曼编码的长度
os.writeChar(c.charAt(0));
os.writeInt(map.get(c).length());
//3.输出字符的哈夫曼编码
os.writeChars(map.get(c));
}
}
//得到文本内容对应的哈夫曼编码串的方法source2HuffmanStr
public String source2HuffmanStr(String str,Map<String,String> map){
StringBuffer buffer = new StringBuffer();
char[] cs = str.toCharArray();
for (char c:cs){
buffer.append(map.get(""+c));//map的键的数据类型为String,所以加上“”
}
return buffer.toString();
}
//将得到的哈夫曼编码串输出,使用方法outDataHuffmanCode
public void outDataHuffmanCode(DataOutputStream os,String dataHuffmanCode){
//1.把这个哈夫曼编码串转换成为对应的byte[]
byte[] bt = this.string2ByteArrays(dataHuffmanCode);
//2.输出byte数组个数
//3.输出byte数组
try {
os.writeInt(bt.length);
os.write(bt);
} catch (IOException e) {
e.printStackTrace();
}
}
//将得到的二进制编码串转换为真正的二进制数组
public byte[] string2ByteArrays(String dataHuffmanCode){
byte[] retBytes = null;
char[] cs = dataHuffmanCode.toCharArray();
int len =cs.length;
int lenByte = 0;
//1.判断整个串的长度能否被8整除
if (len%8==0){
//2.能整除的话,就8位作为一个byte
lenByte=len+1;
retBytes = new byte[lenByte];
for (int i =0;i<lenByte-1;i++){
String s = "";
for (int j =i*8;j<(i+1)*8;j++){
s+=cs[j];
}
retBytes[i]=char2byte(s);//s是作为参数
}
// retBytes最后一位也要加上0
retBytes[lenByte-1]=0;
}
//3.不能整除的话,那么就最后一个字符串后面补0
else{
//3 如果不能整除的话,就补0
lenByte=len/8+2;
retBytes = new byte[lenByte];
int bl = 8-len%8;
for (int k=0;k<bl;k++){
dataHuffmanCode+="0";
}
cs = dataHuffmanCode.toCharArray();
for (int i =0;i<lenByte-1;i++){
String s= "";
for (int j=i*8;j<(i+1)*8;j++){
s+=cs[j];
}
retBytes[i] = char2byte(s);
}
retBytes[lenByte-1]=(byte)bl;
}
return retBytes;
}
//把一个字符串转换为byte
public byte char2byte(String s){
char[] cs = s.toCharArray();
byte ret = 0;
for (int i = 0;i<cs.length;i++){
//计算每一位char代表的真正的数值
byte tempB = (byte)(Byte.parseByte(""+cs[i])*Math.pow(2, cs.length)-i-1);
ret = (byte) (ret+tempB);
}
return ret;
}
public static void main(String[] args) throws IOException {
HuffmanTree t = new HuffmanTree();
try {
t.readFile("123");
} catch (Exception e) {
e.printStackTrace();
}
}
}