2017-03-03 39 views
2

我能夠壓縮各種文件(.jpg,.mp4等),但當我嘗試解壓縮這些非文本文件時程序只是返回一個空的解壓文件......奇怪的是我能夠解壓純文本文件就好了。Java - 我的霍夫曼解壓縮拒絕解壓縮非文本文件(返回空文件)

當我壓縮我的原始文件時,我把重建樹和編碼位所需的數據放在同一個文件中。格式看起來是這樣的:

<n><value 1><frequency 1>...<value n><frequency n>[the compressed bytes] 

其中N是唯一字節(在我的樹葉的AKA數)總數值是字節形式和頻率只是我的葉值是每個字節的頻率/「字符」(頻率是一個int值,因此它由每個頻率4個字節組成)。

我的代碼中的BitFileReader和BitFileWriter只是BufferedOutStream/InputStream的包裝類,並且增加了一點一點的讀/寫功能。

我在下面添加了我的整個霍夫曼代碼,但主要關注的是底部的compress()和decompress()方法。至少我想知道這些方法的邏輯是否正確,如果是這樣,是什麼原因導致它在解壓縮其他文件類型(非純文本文件)時返回空的解壓縮文件?

霍夫曼代碼:

public class HuffmanCode { 


    public static Tree buildTree(int[] charFreqs) { 
     PriorityQueue<Tree> trees = new PriorityQueue<Tree>(); 

     for (int i = 0; i < charFreqs.length; i++){ 
      if (charFreqs[i] > 0){ 
       trees.offer(new Leaf(charFreqs[i], i)); 
      } 
     } 

     //assert trees.size() > 0; 

     while (trees.size() > 1) { 
      Tree a = trees.poll(); 
      Tree b = trees.poll(); 

      trees.offer(new Node(a, b)); 
     } 
     return trees.poll(); 
    } 

    public static void printStruct(Tree tree) { 
     //assert tree != null; 
     if (tree instanceof Leaf) { 
      Leaf leaf = (Leaf)tree; 

      System.out.println(leaf.value + " " + leaf.frequency); 

     } else if (tree instanceof Node) { 
      Node node = (Node)tree; 

      // traverse left 
      printStruct(node.left); 

      // traverse right 
      printStruct(node.right); 
     } 
    } 


    public static void printStruct(Tree tree, StringBuffer prefix) { 
     //assert tree != null; 
     if (tree instanceof Leaf) { 
      Leaf leaf = (Leaf)tree; 

      System.out.println(leaf.value + "\t" + leaf.frequency + "\t" + prefix); 

     } else if (tree instanceof Node) { 
      Node node = (Node)tree; 

      // traverse left 
      prefix.append('0'); 
      printStruct(node.left, prefix); 
      prefix.deleteCharAt(prefix.length()-1); 

      // traverse right 
      prefix.append('1'); 
      printStruct(node.right, prefix); 
      prefix.deleteCharAt(prefix.length()-1); 
     } 
    } 

    public static void fillEncodeMap(Tree tree, StringBuffer prefix, TreeMap<Integer, String> treeMap) { 
     //assert tree != null; 
     if (tree instanceof Leaf) { 
      Leaf leaf = (Leaf)tree; 

      treeMap.put(leaf.value, prefix.toString()); 

     } else if (tree instanceof Node) { 
      Node node = (Node)tree; 

      // traverse left 
      prefix.append('0'); 
      fillEncodeMap(node.left, prefix, treeMap); 
      prefix.deleteCharAt(prefix.length()-1); 

      // traverse right 
      prefix.append('1'); 
      fillEncodeMap(node.right, prefix, treeMap); 
      prefix.deleteCharAt(prefix.length()-1); 
     } 
    } 

    public static void fillDecodeMap(Tree tree, StringBuffer prefix, TreeMap<String, Integer> treeMap) { 
     //assert tree != null; 
     if (tree instanceof Leaf) { 
      Leaf leaf = (Leaf)tree; 

      treeMap.put(prefix.toString(), leaf.value); 

     } else if (tree instanceof Node) { 
      Node node = (Node)tree; 

      // traverse left 
      prefix.append('0'); 
      fillDecodeMap(node.left, prefix, treeMap); 
      prefix.deleteCharAt(prefix.length()-1); 

      // traverse right 
      prefix.append('1'); 
      fillDecodeMap(node.right, prefix, treeMap); 
      prefix.deleteCharAt(prefix.length()-1); 
     } 
    } 



    public static void compress(File file){ 
     try { 
      Path path = Paths.get(file.getAbsolutePath()); 
      byte[] content = Files.readAllBytes(path); 
      TreeMap<Integer, String> encodeMap = new TreeMap<Integer, String>(); 
      File nF = new File(file.getName()+"_comp"); 
      nF.createNewFile(); 
      BitFileWriter bfw = new BitFileWriter(nF); 

      int[] charFreqs = new int[256]; 

      // read each byte and record the frequencies 
      for (byte b : content){ 
       charFreqs[b&0xFF]++; 
      } 

      // build tree 
      Tree tree = buildTree(charFreqs); 

      // build TreeMap 
      fillEncodeMap(tree, new StringBuffer(), encodeMap); 

      // Writes tree structure in binary form to nF (new file) 
      bfw.writeByte(encodeMap.size()); 
      for(int i=0; i<charFreqs.length; i++){ 
       if(charFreqs[i] != 0){ 
        ByteBuffer b = ByteBuffer.allocate(4); 
        b.putInt(charFreqs[i]); 
        byte[] result = b.array(); 

        bfw.writeByte(i); 
        for(int j=0; j<4;j++){ 
         bfw.writeByte(result[j]&0xFF); 
        } 
       } 
      } 

      // Write compressed data 
      for(byte b : content){ 
       String code = encodeMap.get(b&0xFF); 
       for(char c : code.toCharArray()){ 
        if(c == '1'){ 
         bfw.write(1); 
        } 
        else{ 
         bfw.write(0); 
        } 
       } 
      } 
      bfw.close(); 
      System.out.println("Compression successful!"); 

     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public static void decompress(File file){ 
     try { 
      BitFileReader bfr = new BitFileReader(file); 
      int[] charFreqs = new int[256]; 
      TreeMap<String, Integer> decodeMap = new TreeMap<String, Integer>(); 
      File nF = new File(file.getName()+"_decomp"); 
      nF.createNewFile(); 
      BitFileWriter bfw = new BitFileWriter(nF); 
      DataInputStream data = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); 

      int uniqueBytes; 
      int counter = 0; 
      int byteCount = 0; 
      uniqueBytes = data.readUnsignedByte(); 

      // Read frequency table 
      while (counter < uniqueBytes){ 
       int index = data.readUnsignedByte(); 
       int freq = data.readInt(); 
       charFreqs[index] = freq; 
       counter++; 
      } 

      // build tree 
      Tree tree = buildTree(charFreqs); 

      // build TreeMap 
      fillDecodeMap(tree, new StringBuffer(), decodeMap); 

      // Skip BitFileReader position to actual compressed code 
      bfr.skip(uniqueBytes*5); 

      // Get total number of compressed bytes 
      for(int i=0; i<charFreqs.length; i++){ 
       if(charFreqs[i] > 0){ 
        byteCount += charFreqs[i]; 
       } 
      } 

      // Decompress data and write 
      counter = 0; 
      StringBuffer code = new StringBuffer(); 

      while(bfr.hasNextBit() && counter < byteCount){ 
       code.append(""+bfr.nextBit()); 

       if(decodeMap.containsKey(code.toString())){ 
        bfw.writeByte(decodeMap.get(code.toString())); 
        code.setLength(0); 
        counter++; 
       } 
      } 
      bfw.close(); 
      bfr.close(); 
      data.close(); 

      System.out.println("Decompression successful!"); 

     } 

     catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public static void main(String[] args) { 
     File f = new File("test"); 
     compress(f); 
     f = new File("test_comp"); 
     decompress(f); 
    } 
} 

編輯:我找到了原因,但我不知道如何解決,或爲什麼會發生。問題是我的解壓縮方法中的charFreqs []數組永遠不會被填充(它的所有值仍然爲零)。根據數組,所有字節都有零個頻率。

回答

2

我解決了!問題是我的compress()方法中的bfw.writeByte(encodeMap.size())行。它只會將字節寫入文件,但如果encodeMap.size()函數已滿,則可以返回值256。 256的值比一個字節可以容納的值高(bfw.writeByte()實際上接受一個int作爲參數,但它只寫入int的8個最低位,基本上只有一個字節可以容納的位,所以實際上它的範圍的無符號字節0-255)。

我通過更改兩行代碼解決了這個問題。我的compress()方法中的行bfw.writeByte(encodeMap.size())更改爲bfw.writeByte(encodeMap.size()-1),並且我的decompress()方法中的行uniqueBytes = data.readUnsignedByte()更改爲uniqueBytes = data.readUnsignedByte() + 1