这是我把之前学的哈夫曼压缩给系统化,界面化了,变成了一个可以执行的文件,里面的压缩只能给小文件进行压缩(注:这个压缩不会把原文件压坏,所以大家放心的压),如果对大文件压缩的化,会报错,并且压缩后的东西不是个东西,不能还原。而且,压缩后是乱码,可以用来加密哦。
本压缩系统的有点在于:用2个线程来分别进行压缩和解压过程,不会当你按下压缩按钮时,按钮不会弹起来,直到压缩完成后才弹起来;压缩结束后会有提示,方便用户使用。这里把最终的可以执行的程序上传,由于文件超过10m,所以分开上传,大家下载后请把它们解压到同一个路径下。不然不能执行!
?
接着是提供代码的时候了:
这是界面代码:
?
class="java" name="code">package 大二lesson01; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; /** * 哈夫曼压缩主类,实现压缩(英文可压缩) * @author 客 * */ @SuppressWarnings("serial") public class HuffmanTest extends JFrame{ //用来保存中途获取的文件名信息 private static String name; private static File file; //被解压缩文件路径 static String path1 = ""; //解压缩后的文件路径 static String newPath1 = ""; private static JTextField jt1,jt2,jt3,jt4; //压缩文章的路径 static String path = ""; //压缩到的路径 static String newPath = ""; /* * 这是压缩的界面 */ public static void init_ya(){ final JFrame jf_ya=new JFrame(); jf_ya.setTitle("文件压缩系统"); jf_ya.setLocation(530,175); jf_ya.setResizable(false); jf_ya.setSize(300, 300); jf_ya.setIconImage(new ImageIcon("4.gif").getImage());//添加方框的图标 JPanel center = new JPanel(); center.setLayout(new GridLayout(2,3)); JPanel south = new JPanel(); JButton btn_ya = new JButton("开始压缩(Enter)"); JButton btn_fanhui = new JButton("返回(Backspace)"); JButton btn_xuanze1 = new JButton("选择"); JButton btn_xuanze2 = new JButton("选择"); jt1 = new JTextField(6); jt2 = new JTextField(6); JLabel jl1 = new JLabel("选择压缩文件:",JLabel.CENTER); JLabel jl2 = new JLabel("压缩到:",JLabel.CENTER); JLabel jl3=new JLabel(new ImageIcon("6.gif")); jl3.setPreferredSize(new Dimension(30,180)); center.add(jl1); center.add(jt1); center.add(btn_xuanze1); center.add(jl2); center.add(jt2); center.add(btn_xuanze2); south.add(btn_ya); south.add(btn_fanhui); jf_ya.add(jl3,BorderLayout.NORTH); jf_ya.add(center,BorderLayout.CENTER); jf_ya.add(south,BorderLayout.SOUTH); jf_ya.setVisible(true); //返回按钮 btn_fanhui.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { jf_ya.setVisible(false); init_First(); } }); //选择文件按钮 btn_xuanze1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser jc = new JFileChooser(); jc.showOpenDialog(jf_ya); file = jc.getSelectedFile(); name = file.getName(); jt1.setText(file.getAbsolutePath()); } }); //选择路径按钮 btn_xuanze2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser jc = new JFileChooser(); jc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); jc.showOpenDialog(jf_ya); file = jc.getSelectedFile(); jt2.setText(file.getAbsolutePath()); } }); //压缩按钮 btn_ya.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { path=jt1.getText(); String str = "\\压缩"+name; newPath=jt2.getText()+str; HuffmanTest_Thread h_t = new HuffmanTest_Thread(path, newPath); h_t.start(); } }); } /* * 这是主的界面 */ public static void init_First(){ final JFrame jf_first = new JFrame("文件压缩系统"); jf_first.setSize(300,300); jf_first.setLocation(530,175); jf_first.setResizable(false); jf_first.setDefaultCloseOperation(3); jf_first.setIconImage(new ImageIcon("4.gif").getImage());//添加方框的图标 JPanel pa = new JPanel(); JButton btn_ya = new JButton("压缩"); JButton btn_jie = new JButton("解压"); JLabel jl3=new JLabel(new ImageIcon("1.gif")); jl3.setPreferredSize(new Dimension(30,240)); pa.add(btn_ya); pa.add(btn_jie); jf_first.add(jl3,BorderLayout.NORTH); jf_first.add(pa,BorderLayout.CENTER); jf_first.setVisible(true); //解压按钮 btn_jie.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { init_jie(); jf_first.setVisible(false); } }); //压缩按钮 btn_ya.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { init_ya(); jf_first.setVisible(false); } }); } /* * 这是解压缩的界面 */ public static void init_jie(){ final JFrame jf_jie=new JFrame("文件解压系统"); jf_jie.setLocation(530,175); jf_jie.setResizable(false); jf_jie.setSize(300, 300); jf_jie.setIconImage(new ImageIcon("4.gif").getImage());//添加方框的图标 JPanel center = new JPanel(); //中间面板设置表格布局 center.setLayout(new GridLayout(2,2)); JPanel south = new JPanel(); JButton btn_jie = new JButton("开始解压(Enter)"); JButton btn_fanhui = new JButton("返回(Backspace)"); JButton btn_xuanze1 = new JButton("选择"); JButton btn_xuanze2 = new JButton("选择"); jt3 = new JTextField(6); jt4 = new JTextField(6); JLabel jl1 = new JLabel("选择解压文件:",JLabel.CENTER); JLabel jl2 = new JLabel("解压到:",JLabel.CENTER); JLabel jl3=new JLabel(new ImageIcon("8.gif")); jl3.setPreferredSize(new Dimension(30,180)); center.add(jl1); center.add(jt3); center.add(btn_xuanze1); center.add(jl2); center.add(jt4); center.add(btn_xuanze2); south.add(btn_jie); south.add(btn_fanhui); jf_jie.add(jl3,BorderLayout.NORTH); jf_jie.add(center,BorderLayout.CENTER); jf_jie.add(south,BorderLayout.SOUTH); jf_jie.setVisible(true); //选择文件的按钮 btn_xuanze1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser jc = new JFileChooser(); jc.showOpenDialog(jf_jie); file = jc.getSelectedFile(); name = file.getName(); jt3.setText(file.getAbsolutePath()); } }); //选择路径的按钮 btn_xuanze2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JFileChooser jc = new JFileChooser(); jc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); jc.showOpenDialog(jf_jie); file = jc.getSelectedFile(); jt4.setText(file.getAbsolutePath()); } }); //返回按钮 btn_fanhui.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { jf_jie.setVisible(false); init_First(); } }); //解压按钮 btn_jie.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { path1 = jt3.getText(); String str = "\\解"+name; newPath1=jt4.getText()+str; HuffmanTestThread ht = new HuffmanTestThread(path1, newPath1); ht.start(); } }); } /** * 程序入口,主方法 * @param args * @throws IOException */ public static void main(String[] args) throws IOException{ init_First(); } }
?这是压缩的线程和算法代码:
?
package 大二lesson01; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.PriorityQueue; import javax.swing.JOptionPane; public class HuffmanTest_Thread extends Thread{ static HeadInfo[] head; //记录总共有多少个字符 static int codeNum; //压缩文章的路径 private String path = ""; //压缩到的路径 private String newPath = ""; //记录文章中字符和频率的数组,下标表示字符的ASCII码,数组内容表示该字符的出现次数 static int[] byteCount = new int[256]; //哈夫曼树的根节点 static hfmNode root; //传入“给叶子节点设置编码的方法”中 static String s = ""; //存储ascii为下标值的字符的编码对象,它是码表,是初始的编码,不补0的 static Code SaveCode[] = new Code[256]; //存储ascii为下标值的字符的编码需要的补0数 static int zeroCount[] = new int[256]; static FileInputStream fis; static FileOutputStream fos; //记录文件内容所有的原始编码的字符串,用以统计文件末尾的补零个数 static int allString = 0; //文件末尾补零的个数 static int fileAddZero; public HuffmanTest_Thread(String path,String newPath){ this.path = path; this.newPath = newPath; } public void run(){ try { //遍历文件统计每个字符的出现次数,并且记录字符的ASCII码 countWeight(path); //根据字符出现次数,建立哈夫曼树,方法末尾得到树的根节点 createTree(); //给叶子节点设置编码 setCode(root, s); //写入文件头信息 writeHead(newPath); //写入文件 writeFile(path); JOptionPane.showMessageDialog(null, "Done"); } catch (IOException e1) { e1.printStackTrace(); } } /** * 读取文件统计字符和字符的出现频率 * @param path 要压缩的文件路径 * @throws IOException */ public static void countWeight(String path) throws IOException{ fis = new FileInputStream(path); while(fis.available()>0){ int i = fis.read(); byteCount[i]++;//i是字符的ASCII码,数组内容是该字符的出现次数 } } /** * 根据字符频率,将字符创建成结点加入到优先队列中,建立哈夫曼树 */ public static void createTree(){ //使用优先队列 PriorityQueue<hfmNode> nodeQueue = new PriorityQueue<hfmNode>(); for(int i=0;i<byteCount.length;i++){ // System.out.println(byteCount[i]); if(byteCount[i]!=0){//文件中出现次数不为0的字符,建立哈夫曼结点,存入队列 hfmNode node = new hfmNode(i,byteCount[i]); nodeQueue.add(node); } } //根据优先队列的排序,构建哈夫曼树 while(nodeQueue.size()>1){//当队列中的元素个数大于1 //取得最小的两个,并删除掉 hfmNode min1 = nodeQueue.poll(); hfmNode min2 = nodeQueue.poll(); //建立新的结点 hfmNode result = new hfmNode(0,min1.getTimes()+min2.getTimes()); //设置新结点的左右子结点 result.setlChild(min1); result.setrChild(min2); //新结点加入到优先队列中 nodeQueue.add(result); } //循环结束后队列中只有一个元素,该元素就是根节点,获取到但不删除 root = nodeQueue.peek(); // System.out.println("根!"+root.toString()); } /** * 给叶子节点设置编码 * @param root * @param s */ public static void setCode(hfmNode root,String s){ //如果左右子结点都为空,即为叶子节点 if(root.getlChild()==null&&root.getrChild()==null){ //实例化一个code对象,设置编码和长度 Code huffman_code = new Code(); huffman_code.setCode(s); //把编码code对象放到SaveCode数组中下标为字符ascii值 SaveCode[root.getAscii()] = huffman_code; //输出编码信息 // System.out.println(SaveCode[root.getAscii()].toString()); } //如果左子结点不为空,递归 if(root.getlChild()!=null){ setCode(root.getlChild(),s+'0'); } //如果右子结点不为空,递归 if(root.getrChild()!=null){ setCode(root.getrChild(),s+'1'); } } /** * 写入文件头信息,写入的编码是补零后的编码 * 然后把码表写入文件头信息 * @throws IOException */ public static void writeHead(String newPath) throws IOException{ fos = new FileOutputStream(newPath); //统计文件总共用到的字符总数,即编码个数 int codeNum = 0; for(int i=0;i<256;i++){ if(SaveCode[i]!=null){ codeNum++; allString += byteCount[i]*SaveCode[i].getCode().length(); } } //写入总文件中总共有多少个用到的字符 fos.write(codeNum); // System.out.println(codeNum); //循环输出码表,包括(按顺序):字符、补了几个0、编码含有的byte个数、编码(整型)。 for(int i=0;i<256;i++){ //如果该字符在文件中出现过,即编码不为空 if(SaveCode[i]!=null){ //写入:字符的ASCII码值 fos.write(i); // System.out.print(i+"\t"+(char)i); //第i个字符的补零数 if(SaveCode[i].getCode().length()%8!=0){//如果编码字符串程度不是8的整倍数 zeroCount[i] = 8-SaveCode[i].getCode().length()%8;//设置补零的个数 }else{//如果是8的整倍数 zeroCount[i] = 0; } //写入:每个编码末尾补了几个0 fos.write(zeroCount[i]); //补零后的编码,写入头信息 String addZero = SaveCode[i].getCode(); // System.out.println(SaveCode[i].getCode()+"需要补零的个数"+zeroCount[i]); for(int j=0;j<zeroCount[i];j++){//循环次数为:需要补零的个数 //每次在编码后补一个0 // SaveCode[i].setCode(SaveCode[i].getCode()+'0'); addZero = addZero+'0'; } //在补0完成后,写入:编码有几个byte fos.write(addZero.length()/8); // System.out.print(addZero.length()/8+"\t"); //对于补齐0的编码,每次取出一个字节 for(int j=0;j<addZero.length()/8;j++){ String subString = ""; //取出一个字节中的8位赋值给codeString for(int k=j*8;k<(j+1)*8;k++){ subString = subString+addZero.charAt(k); } //读取每个字节,将01串转化成整型数 int cs = changeString(subString); fos.write(cs); // System.out.print(cs+" "); }//每个字符读取完毕 // System.out.println("\t"+zeroCount[i]); } } if(allString%8==0){ fos.write(0); }else{ fileAddZero = 8-allString%8; fos.write(fileAddZero); // System.out.println("文章末尾补零"+fileAddZero); } } /** * 读文件并写入内容 * @throws IOException */ public static void writeFile(String path) throws IOException{ // System.out.println("xxxxxxx"); //等待中的编码字符串 String str = ""; fis = new FileInputStream(path); //如果文件可读内容大于0 while(fis.available()>0){ //读取一个字节字符 int i = fis.read(); //得到该字符的码表中的编码----没有补零的,加入到等待中的字符串后 str = str+SaveCode[i].getCode(); // System.out.println("!!~~!!"+str); //如果该编码长度大于等于8 while(str.length()>=8){ //截取8位编码的字符串 String subString = ""; for(int j=0;j<8;j++){ subString = subString+str.charAt(j); } //读取每个字节,将01串转化成整型数 int cs = changeString(subString); fos.write(cs); //删除str的前八位 String tranString = str; str = ""; for(int j=8;j<tranString.length();j++){ str = str+tranString.charAt(j); } } // System.out.println("sssssssssssssssssssss"+str); } //文件末尾最后的字符串 // System.out.println("mm"+str); for(int t=0;t<fileAddZero;t++){ str = str+'0'; } // System.out.println(str); //写入文件 int cs = changeString(str); fos.write(cs); } /** * 将8个字符的字符串转换成01串对应的整数 * @param s * @return */ private static 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); } }
??这是解压缩的线程和算法代码:
package 大二lesson01; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import javax.swing.JOptionPane; public class HuffmanTestThread extends Thread{ //被解压缩文件路径 private String path1 = ""; //解压缩后的文件路径 private String newPath1 = ""; static HeadInfo[] head; //记录总共有多少个字符 static int codeNum; static int[] byteCount = new int[256]; static FileInputStream fis; static FileOutputStream fos; //文件末尾补零的个数 static int fileAddZero; public HuffmanTestThread(String path1,String newPath1){ this.path1=path1; this.newPath1=newPath1; } public void run(){ try { readHead(path1); //读取文件内容并输出 readFile(path1,newPath1); JOptionPane.showMessageDialog(null, "Done"); } catch (IOException e1) { e1.printStackTrace(); } } /** * 读取文件头信息,按写入的顺序依次读出: * 1、字符种类总数, * 2、字符ASCII码,编码末尾补零数,字符占字节数,编码 * 3、文件末尾补零数 * * @throws IOException */ public static void readHead(String path) throws IOException{ fis = new FileInputStream(path); //读取字符种类总数 codeNum = fis.read(); // System.out.println(codeNum); //实例化记录文件头信息的数组,数组长度为字符种类总数 head = new HeadInfo[codeNum]; // System.out.println("ascii\t字节数\t补零数\t编码"); //以字符种类总数作为循环限制条件,读取每个字符的编码细则 for(int i=0;i<codeNum;i++){ //创建文件头信息对象 HeadInfo info = new HeadInfo(); //读取字符ASCII码,给对象设置ASCII码 info.setAscii(fis.read()); // System.out.print(info.getAscii()+"\t"+(char)info.getAscii()+" "); //读取编码末尾补零数,并给对象设置 info.setZeroNum(fis.read()); //读取编码所占字节数,给对象设置 info.setByteNum(fis.read()); // System.out.print(info.getByteNum()+"\t"); //读取当前编码的每个字节 for(int j=0;j<info.getByteNum();j++){ int code_int = fis.read(); //读取到的整数转换成String---0,1串,该01串不会以0开头 String code_string = Integer.toBinaryString(code_int); // System.out.println("s++"+code_string); int length = code_string.length(); //01串不足8位的在前面补零 if(length%8!=0){ for(int k=0;k<8-length;k++){ code_string = '0'+code_string; } } //给info对象设置编码,含补零,每个字节的前面补零的01串相加 info.setCode(info.getCode()+code_string); } //得到完整的补零后的01串,即文件头信息存储的编码 // System.out.print(info.getZeroNum()+"\t"); //根据编码末尾补零个数,恢复编码不补零的编码,即码表中存储的编码, for(int t=0;t<info.getZeroNum();t++){ info.setCode(info.getCode().substring(0, info.getCode().length()-1)); } // System.out.println("去0后 "+info.getCode()); //将info对象添加到数组中 head[i] = info; } //读取头信息的最后一个内容:文章末尾的补零的个数 fileAddZero = fis.read(); // System.out.println("文件末尾补零"+fileAddZero); } /** * 读取文件内容,写入新文件,完成解压缩 * @throws IOException */ public static void readFile(String path,String newPath) throws IOException{ fos = new FileOutputStream(newPath); //等待转换的字符串 String str = ""; //当文件可读信息大于零时 while(fis.available()>0){ //读取一个字节,赋值给整数txt int txt = fis.read(); //将该整数转换成01串,该字符串不会以0开头 String temp = Integer.toBinaryString(txt); // System.out.println(temp+" nnnn"); //给转换成的01串,前面补零成完整的8位的字符串 int frontAddZero = temp.length()%8; if(frontAddZero!=0){ for(int i=0;i<8-frontAddZero;i++){ temp = '0'+temp; } } // System.out.println(temp+">>>>>>>>>>>>"); //加到等待转换的字符串后,等待转换 str = str+temp; // System.out.println(str+" !!!!等待转换!!!!"); for(int j=0;j<codeNum;j++){ //判断当前等待转换的字符串,是否以码表中的某个编码开头 if(str.startsWith((head[j].getCode()))){ //如果是,写入这个编码对应的字符 // System.out.println(str+" ======== "+head[j].getCode()); // System.out.println("qqqq"+i); // System.out.println((char)head[j].getAscii()); fos.write(head[j].getAscii()); // //将等待转换的字符串去掉已经写入的编码部分 String tran = str; str = ""; for(int k=head[j].getCode().length();k<tran.length();k++){ str = str+tran.charAt(k); } //设置循环从头开始判断现在的str是否以某编码开头 j = -1; //如果此时已经没有可读的文件信息了 if(fis.available()==0){ //将文件末尾添加的0的个数,在str中删去 String tra = str; str = ""; for(int t=0;t<tra.length()-fileAddZero;t++){ str = str+tra.charAt(t); } //如果删去之后字符串空了,那么跳出循环,文件读取完毕 if(str==""){ break; } //如果str不为空,那么继续j=-1的地方循环判断是否以某编码开头 } } }//for循环 }//while循环 //确认文件读取完毕 // System.out.println(str.length()+" 最后str的长度"); } }
?以下三个是辅助类:
package 大二lesson01; public class Code { //哈夫曼编码 private String code = ""; /** * 输出Code类对象的方法 */ public String toString(){ return "编码:"+code+" 长度:"+code.length(); } /** * 得到编码内容 * @return */ public String getCode() { return code; } /** * 设置编码内容 * @param code */ public void setCode(String code) { this.code = code; } }
?**********************
package 大二lesson01; public class hfmNode implements Comparable<hfmNode>{ //字符的ASCII码值 private int ascii; //字符的出现次数 private int times; //当前结点的左子结点 private hfmNode lChild; //当前结点的右子结点 private hfmNode rChild; /** * 构造方法 * @param i ASCII的值 * @param times 文件中的出现次数 */ public hfmNode(int ascii,int times){ this.ascii = ascii; this.setTimes(times); } /** * 得到结点字符ASCII值得方法 * @return ASCII码值 */ public int getAscii() { return ascii; } /** * 设置结点字符的ASCII值 * @param ascii */ public void setAscii(int ascii) { this.ascii = ascii; } /** * 得到当前结点字符出现次数的方法 * @return */ public int getTimes() { return times; } /** * 设置当前结点字符出现次数的方法 * @param times */ public void setTimes(int times) { this.times = times; } /** * 得到当前结点左孩子的方法 * @return 左子结点 */ public hfmNode getlChild() { return lChild; } /** * 设置当前结点左孩子的方法 * @param lChild */ public void setlChild(hfmNode lChild) { this.lChild = lChild; } /** * 得到当前结点右孩子的方法 * @return 右子结点 */ public hfmNode getrChild() { return rChild; } /** * 设置当前结点右孩子的方法 * @param rChild */ public void setrChild(hfmNode rChild) { this.rChild = rChild; } /** * 输出hfmNode类对象--哈夫曼结点的方法 */ public String toString(){ return "ascii="+(char)ascii+" times="+times; } /** * 定义在队列中进行排序比较的属性 */ public int compareTo(hfmNode o) { return times-o.getTimes(); } }
?***********************
package 大二lesson01; public class HeadInfo { //字符ASCII码 private int ascii; //字符编码占用字节数 private int byteNum; //编码末尾补零数 private int zeroNum; //编码 private String code = ""; /** * 得到ASCII码 * @return */ public int getAscii() { return ascii; } /** * 设置ASCII码 * @param ascii */ public void setAscii(int ascii) { this.ascii = ascii; } /** * 得到编码占字节数 * @return */ public int getByteNum() { return byteNum; } /** * 设置编码占字节数 * @param byteNum */ public void setByteNum(int byteNum) { this.byteNum = byteNum; } /** * 得到编码 * @return */ public String getCode() { return code; } /** * 设置编码 * @param code */ public void setCode(String code) { this.code = code; } /** * 得到编码末尾补零个数 * @return */ public int getZeroNum() { return zeroNum; } /** * 设置编码末尾补零个数 * @param zeroNum */ public void setZeroNum(int zeroNum) { this.zeroNum = zeroNum; } }
?就这么多了,接下来附上我的基本的界面图片:
?
?
?接下来,我将改进算法并且给部分按钮加上键盘监听器,如果大家有什么好的建议或意见,欢迎留言,我会积极采纳的!