本文做的压缩软件其压缩算法是哈夫曼压缩, 不了解哈夫曼编码基本思想的同学请自行百度。
哈夫曼压缩软件基本思路:
一.压缩
1.统计文件中各字节出现的次数。
2.根据第一步结果建立哈夫曼树
3.得到每个字节的哈夫曼编码
4.对文件每个字节进行编码,以每8位为一字节写入到压缩后的文件
压缩文件内容:
新的哈夫曼编码表(原哈夫曼表的每对KEY 与VALUE 调换位置)+文件编码+补0个数(占一字节)
二:解压
1.读入哈夫曼编码表
2.每读入一个字节就将其转换为其二进制形式的字符串,判断是否为哈夫曼码
3.第二步的结果如果哈夫曼编码,则译码并写入解压文件,否则继续读入
基本思路如上,下面看看需要注意的问题
1.ObjectInputStream 的available方法 并不能得到流中剩余字节数
如: System.out.println("ois ava: " + ois.available());
reMap = (HashMap<String, Byte>) ois.readObject();
System.out.println("ois ava2: " + ois.available());
结果为 0 和 1024(不同文件结果可能不同)
2.若用BufferedIntputStream 包装ObjectInputStream 则 bis(BufferedIntputStream的对象)的available().方法的最大值也为1024
3.
File file=new File("F:\\JavaTest\\source.txt"); FileInputStream fis=new FileInputStream(file); BufferedInputStream bis=new BufferedInputStream(fis); int b; b=fis.read(); System.out.println(b); b=bis.read(); System.out.println(b);
source.txt中存的是abcd。
输出的结果是 97 98 。
因此 用BufferedInputStream包装FileInputStream以后,BufferedInputStream的读入会随着FileInputStream的读入变化 。 反过来不成立。如:
File file=new File("F:\\JavaTest\\source.txt"); FileInputStream fis=new FileInputStream(file); BufferedInputStream bis=new BufferedInputStream(fis); int b; b=bis.read(); System.out.println(b); b=fis.read(); System.out.println(b);
输出为 97 -1
4.切忌不要用如下形式读入数据
FileInputStream fis=new FileInputStream(file); BufferedInputStream bis=new BufferedInputStream(fis); for(int i=0;i<bis.available();i++)//因为没执行一次read方法 available返回的数都会变化 { int t=bis.read(); System.out.printlen(t); }
好了 。下面看代码
package data_0719哈夫曼树; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Comparator; import java.util.HashMap; import java.util.PriorityQueue; import java.util.Set; /** * 该类实现了哈夫曼压缩功能 1.统计各字符出现频度 2.创建哈夫曼树 3.进行哈夫曼编码 4.根据哈夫曼编码进行压缩 * * @author ZhangZunC * */ public class HuffMan { //6810ms /** * 压缩文件 * @param fileIn * 要压缩的文件 * @param fileOut * 压缩文件的存放地址 */ public void toRar(File fileIn, File fileOut) { TreeNode huffTree = new TreeNode(0);// 哈夫曼树 TreeNode[] treeArray = new TreeNode[257];// 保存频度信息的树数组 // 统计频度 countFile(fileIn, treeArray); // 建立优先队列 NodeCompare nodecompare = new NodeCompare(); PriorityQueue<TreeNode> queue = new PriorityQueue<TreeNode>(12, nodecompare); for (int i = 0; i < treeArray.length; i++) { if (treeArray[i].data != 0) queue.add(treeArray[i]); } // 创建树 huffTree = creatTree1(queue); HashMap<Byte, String> map = new HashMap(); // printTree(huffTree); // 编码 huffCode(map, huffTree, ""); // 输出编码 System.out.println("********** 哈夫曼编码表: *******************"); Set<Byte> nodes = map.keySet(); for (Byte node : nodes) { String t = map.get(node); System.out.println(node + "<>" + t); } System.out.println("***********************************************"); // 压缩文件到fileOut huffmanrar(fileIn, fileOut, map); System.out.println("压缩前文件大小:" + fileIn.length() + " bytes 压缩后的文件的大小:" + fileOut.length() + " bytes"); }// main public void unrar(File rarFile, File unRarFile) { long startTime=System.currentTimeMillis(); // 读入压缩文件 FileInputStream fis; try { fis = new FileInputStream(rarFile); // 得到保存哈弗曼编码的哈希表 (与压缩时的哈希表 映射正好相反) HashMap<String, Byte> reMap = new HashMap(); ObjectInputStream ois = new ObjectInputStream(fis); reMap = (HashMap<String, Byte>) ois.readObject(); // BufferedInputStream bis = new BufferedInputStream(ois); FileOutputStream fos = new FileOutputStream(unRarFile); BufferedOutputStream bos = new BufferedOutputStream(fos); // 将字符串(哈夫曼编码)翻译 String temp = ""; Byte wByte; int bt = ois.read(); while (bt != -1) { int readLen = 8; //只剩下最后一个了 if(fis.available()==1)//这里不能用ois.available() { readLen=8-ois.read();//0的个数 } for (int j = 0; j < readLen; j++) { temp +=byte2String((byte) bt).charAt(j);// 每次取一个字符 // 如果包含编码 if (reMap.containsKey(temp)) { //转换成源文件的字节 wByte = reMap.get(temp); bos.write(wByte); temp=""; } } bt = ois.read(); } long endTime=System.currentTimeMillis(); System.out.println("解压成功 所花时间: "+(endTime-startTime)+"ms"); fis.close(); bos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 根据哈夫曼表 压缩文件 * * @param fileIn * 要压缩的文件 * @param fileOut * 压缩后的文件 * @param map * 哈希表 */ private void huffmanrar(File fileIn, File fileOut, HashMap<Byte, String> map) { try { FileOutputStream fos = new FileOutputStream(fileOut); ObjectOutputStream oos = new ObjectOutputStream(fos); //将HashMap逆转然后写入到文件 HashMap<String, Byte> reMap = new HashMap(); Set<Byte> nodes = map.keySet(); for (Byte node : nodes) { // System.out.println(node+"<>"+map.get(node)); reMap.put(map.get(node), node); } oos.writeObject(reMap); // 将文件内容每8位为一字节写到文件 FileInputStream fis = new FileInputStream(fileIn); BufferedInputStream bis = new BufferedInputStream(fis); String sTemp = ""; int num=0; int tb = bis.read(); while (tb != -1) { //System.out.println((byte)tb+" "+map.get((byte)tb)); sTemp += map.get((byte)tb); //大于8位则将前8位写入 while (sTemp.length() >= 8) { //System.out.println("sTemp "+sTemp); String wr = sTemp.substring(0, 8); sTemp = sTemp.substring(8, sTemp.length()); byte wt = string2Int(wr); oos.write(wt); num++; } tb = bis.read(); } System.out.println("num: "+(num+1)); byte zeroAdd = 0; if (sTemp.length() != 0) { while (sTemp.length() != 8) { sTemp += "0"; zeroAdd++; } } oos.write(string2Int(sTemp)); oos.write(zeroAdd); fis.close(); oos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 用优先队列创建树 * * @param treeArray * @return */ private TreeNode creatTree1(PriorityQueue<TreeNode> queue) { TreeNode huffTree = new TreeNode(0); while (queue.size() > 1) { TreeNode t1 = queue.poll(); TreeNode t2 = queue.poll(); huffTree = huffTree.Union(t1, t2); queue.add(huffTree); } return huffTree; } /** * 哈夫曼编码 * * @map 哈希表 * @param huffTree * 当前树 * @param code * 哈夫曼码 */ private void huffCode(HashMap<Byte, String> map, TreeNode huffTree, String code) { // 如果是叶子节点 if (huffTree.left == null && huffTree.right == null) { map.put(huffTree.index, code); // System.out.println("index: "+huffTree.index+" code : "+code); return; } else { // 访问左子树 if (huffTree.left != null) huffCode(map, huffTree.left, code + "0"); // 访问右子树 if (huffTree.right != null) huffCode(map, huffTree.right, code + "1"); } } /** * 统计文件夹的每个字节出现的次数 * * @param fileIn */ private void countFile(File fileIn, TreeNode[] root) { try { FileInputStream fis = new FileInputStream(fileIn); BufferedInputStream bis = new BufferedInputStream(fis); // 初始化 for (int i = 0; i < root.length; i++) root[i] = new TreeNode(0); HashMap<Integer, TreeNode> map = new HashMap(); int inum = bis.available(); int nodeNum = 0; for (int i = 0; i < 257; i++) root[i] = new TreeNode(); for (int i = 0; i < inum; i++) { int t = bis.read(); if (map.containsKey(t)) { map.get(t).data += 1; // System.out.println("index: "+map.get(t).index+"data "+ // map.get(t).data); } else { root[nodeNum].index = (byte)t; root[nodeNum].data = 1; map.put(t, root[nodeNum]); nodeNum++; } } fis.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 字节转int * @param b * @return */ private int byteToint(byte b[]) { int t1 = (b[3] & 0xff) << 24; int t2 = (b[2] & 0xff) << 16; int t3 = (b[1] & 0xff) << 8; int t4 = b[0] & 0xff; // System.out.println(b[1]&0xff);//输出的是一个整形数据 // 在java中,int和比int位数来的小的类型b,如byte,char等,都是先把小类型扩展成int再来运算, // return( t1<<24)+(t2<<16)+(t3<<8)+t4;//必须加括号 return t1 + t2 + t3 + t4; } /** * int 转换byte * @param a * @param len * @return */ private byte[] intTobyte(int a, int len) { byte[] t = new byte[len]; t[0] = (byte) ((a & 0xff)); switch (len) { case 4: t[3] = (byte) ((a & 0xff000000) >> 24); case 3: t[2] = (byte) ((a & 0xff0000) >> 16); case 2: t[1] = (byte) ((a & 0xff00) >> 8); case 1: break; } return t; } /** * 将byte 转成其二进制的字符串 * @param b * @return */ private String byte2String(byte b) { String s = ""; for (int i = 7; i >= 0; i--) { int temp = b >> i & 1; s += temp; } return s; } /** * 输出哈夫曼树 * * @param node */ private void printTree(TreeNode node) { if (node != null) { printTree(node.left); System.out.println(node.data); printTree(node.right); } } /** * 将8位字符串转换为整数 * * @param s * @return */ private byte string2Int(String s) { byte ans = 0; for (int i = 0; i < s.length(); i++) { int t = s.charAt(i) - 48; if (i != s.length() - 1) { if ((t == 1)) t = 2; ans += Math.pow(t, s.length() - i - 1); } else { ans += t; } } if (ans > 127) System.out.println("error"); return ans; } }
package data_0719哈夫曼树; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; public class HaffManFrame extends JFrame { JButton rar; JButton unrar; ButtonListener bl; public HaffManFrame() { // 设置窗体相关 this.setSize(400, 300); this.setDefaultCloseOperation(3); this.setVisible(true); this.setLayout(new FlowLayout()); // 初始化按钮以及添加监听器 rar = new JButton("压缩文件"); this.add(rar); unrar = new JButton("解压文件"); this.add(unrar); bl = new ButtonListener(); rar.addActionListener(bl); unrar.addActionListener(bl); } /** * 按钮监听器类 * * @author ZhangZunC * */ class ButtonListener implements ActionListener { private JFileChooser fileChooser = null; private HuffMan huffMan = null; private File chooseFile = null; private File saveFile = null; public ButtonListener() { // 初始化文件选择框 fileChooser = new JFileChooser(); // 设置当前目录 fileChooser.setCurrentDirectory(new File("F:\\JavaTest")); huffMan = new HuffMan(); } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("压缩文件")) { yasuo(); } if (e.getActionCommand().equals("解压文件")) { jieya(); } } public void yasuo() { // 如果点击的是确定按钮 if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { chooseFile = fileChooser.getSelectedFile(); // 选择要保存的目录 if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { saveFile = fileChooser.getSelectedFile(); // 压缩文件 huffMan.toRar(chooseFile, saveFile); System.out.println("压缩成功"); } } } public void jieya() { //解压文件 if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { chooseFile = fileChooser.getSelectedFile(); // 选择要保存的目录 if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { saveFile = fileChooser.getSelectedFile(); huffMan.unrar(chooseFile, saveFile); System.out.println("解压成功"); } } // unrar(fileOut,unRarFile,map); } } public static void main(String[] args) { HaffManFrame hmf = new HaffManFrame(); } }
package data_0719哈夫曼树; import java.util.Comparator; /** * 比较类 * @author ZhangZunC * */ class NodeCompare implements Comparator<TreeNode> { @Override public int compare(TreeNode o1, TreeNode o2) { // TODO Auto-generated method stub if (o1.data > o2.data) return 1; if (o1.data < o2.data) return -1; // if(o1.data==o2.data) return 0; } }
软件还存在的问题:
压缩大的文件时,速度会非常慢。
解决方案:
使用消费者模型,构建双缓冲读入写出。现在正在尝试写。。。。。
欢迎指点交流!