1、先读取文件,统计每个字符在文件中出现的次数。读取文件应用到文件输出流(FileInputStream),在读的过程中,每次读取只读一个字节(byte(8个bit)),根据每次读取的字节可以统计文件中每个字符出现的次数。
2、根据统计出来的字符个数(作为节点的权值),建立哈夫曼树。要建立哈夫曼树,首先要对这些数字进行排序,每次取最小的两个数作为子节点,生产一个父节点,并且父节点的数字是两个子节点的数字之和,去除已经取的这两个数,并把生成的父节点的数添加进去重新排序,再次取出两个最小的数进行上面的操作,直到要排序的数字只剩下一个,操作结束,同时建好哈夫曼树(提示:可以把数字放到队列或数组中排序)。
/** * 创建哈弗曼树 * @param nodes * @return */ public static <T> Node<T> createTree(List<Node<T>> nodes){ while(nodes.size() > 1){ Collections.sort(nodes); Node<T> left = nodes.get(nodes.size()-1); Node<T> right = nodes.get(nodes.size()-2); Node<T> parent = new Node<T>(null, left.getWeight()+right.getWeight()); parent.setLeftChild(left); parent.setRightChild(right); nodes.remove(left); nodes.remove(right); nodes.add(parent); } return nodes.get(0); }
3、遍历哈夫曼树,把每一个节点转换成二进制。遍历哈夫曼树可以用递归,也可以用循环。
(1)、用循环遍历哈夫曼树(提示:用字符串的形式表示二进制)。
遍历时,应用到两个集合框架,一个是ArrayList,另一个是ArrayDeque。先从根节点开
始遍历,把根节点先存到ArrayDeque中,用一个while循环并确保ArrayDeque中存到有节点。
然后把ArrayDeque中的节点放到ArrayList中且把这个节点从ArrayDeque中删除,并判断这个
节点是否有左右节点,若有把节点放到ArrayDeque中,并且左节点字符串添加一个0,字符串
长度加1,右节点字符串添加一个1,字符串长度加1。最后返回ArrayList。
public static <T> List<Node<T>> breadth(Node<T> root){ List<Node<T>> list = new ArrayList<Node<T>>(); Queue<Node<T>> queue = new ArrayDeque<Node<T>>(); String str = ""; if(root != null){ queue.offer(root); root.setStr(str); root.setN(0); } while(!queue.isEmpty()){ list.add(queue.peek()); Node<T> node = queue.poll(); if(node.getLeftChild() != null){ queue.offer(node.getLeftChild()); str = node.getStr()+"0"; node.getLeftChild().setStr(str); int n = node.getN()+1; node.getLeftChild().setN(n); } if(node.getRightChild() != null){ queue.offer(node.getRightChild()); str = node.getStr()+"1"; node.getRightChild().setStr(str); int n = node.getN()+1; node.getRightChild().setN(n); } } return list; }
(2)、递归遍历哈夫曼树(中序遍历)。
先访问根节点,分别递归左右节点。同样是递归左节点时,左节点字符串添加一个0,字
符串长度加1,递归右节点时,右节点字符串添加一个1,字符串长度加1。
/** * 访问节点 * */ public static <T> void visit(Node<T> p) { System.out.print(p.getData() + " "); } /** * 递归实现前序遍历 * @param <T> * @param root */ protected static <T> void preorder(Node<T> p) { if (p != null) { visit(p); preorder(p.getLeftChild()); preorder(p.getRightChild()); } }
4、每八个一组转换成可写的形式。
先根据每个字符的权值和二进制长度计算文件中字符转换成二进制的总长度,若总长度不是8
的倍数,在最后面补零凑够8的倍数,并把补零的个数在最后用八位的二进制表示。
然后每八位一组转换成可写的形式往文件中写。我认为最好把补零的个数和要不补的0写在文
件的前面,这样可以在解压时先把补得0删除。在文件的开始最好是先把每一个字符及对应的码值
写进去,以便于解压。
/** * 每八位转换成二进制 * @param s * @return */ public int chengString(String s){ System.out.println("每次取得的八位字符:"+s); int n = ((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); return n; }
总结,第一次了解文件的压缩,以前经常用文件压缩,但从没有考虑过它的压缩过程(很多事情就发生在我们身边,只是不太注意和深究它)。在做的过程中,遇到很多问题:如何建树,怎么转换成二进制等。解决问题有很多方式,可以查找资料,可以在网上搜索,也可以与别人交流,另外遇到问题可以先分析问题,把一个问题进行分解,变成小问题。同时了解到自己对集合框架的运用不够灵活。要说收获,对如何建哈夫曼树有了了解,对队列Queue的添加、检查和移除有了了解等等。