2009-07-02 52 views
14

我有一個應用程序,我正在讀寫數百萬字節的小塊數據(幾百字節)。我想根據示例數據文件生成壓縮字典,並在讀取和寫入小塊時永久使用該字典。我傾向於LZW壓縮算法。維基百科頁面(http://en.wikipedia.org/wiki/Lempel-Ziv-Welch)列出了用於壓縮和解壓縮的僞代碼。修改它看起來相當簡單,這樣字典創建就是一個單獨的代碼塊。所以我有兩個問題:使用預先計算好的字典的小塊無損壓縮

  1. 我在正確的軌道上還是有更好的方法嗎?
  2. 爲什麼在解壓縮步驟中LZW算法會添加到字典中?我可以省略,還是會在字典中失去效率?

謝謝。

更新:現在我在想,理想的情況是找到一個庫,它可以讓我將字典與壓縮數據分開存儲。有這樣的事嗎?

更新:我結束了代碼在http://www.enusbaum.com/blog/2009/05/22/example-huffman-compression-routine-in-c和適應它。我是克里斯在該頁面的評論。我把我的mod發回給那位博客作者,但我還沒有回覆。我看到的代碼壓縮率並不令人印象深刻。也許這是由於8位樹大小。

更新:我將它轉換爲16位,壓縮比較好。它也比原始代碼快得多。

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 

namespace Book.Core 
{ 
    public class Huffman16 
    { 
    private readonly double log2 = Math.Log(2); 

    private List<Node> HuffmanTree = new List<Node>(); 

    internal class Node 
    { 
     public long Frequency { get; set; } 
     public byte Uncoded0 { get; set; } 
     public byte Uncoded1 { get; set; } 
     public uint Coded { get; set; } 
     public int CodeLength { get; set; } 
     public Node Left { get; set; } 
     public Node Right { get; set; } 

     public bool IsLeaf 
     { 
     get { return Left == null; } 
     } 

     public override string ToString() 
     { 
     var coded = "00000000" + Convert.ToString(Coded, 2); 
     return string.Format("Uncoded={0}, Coded={1}, Frequency={2}", (Uncoded1 << 8) | Uncoded0, coded.Substring(coded.Length - CodeLength), Frequency); 
     } 
    } 

    public Huffman16(long[] frequencies) 
    { 
     if (frequencies.Length != ushort.MaxValue + 1) 
     { 
     throw new ArgumentException("frequencies.Length must equal " + ushort.MaxValue + 1); 
     } 
     BuildTree(frequencies); 
     EncodeTree(HuffmanTree[HuffmanTree.Count - 1], 0, 0); 
    } 

    public static long[] GetFrequencies(byte[] sampleData, bool safe) 
    { 
     if (sampleData.Length % 2 != 0) 
     { 
     throw new ArgumentException("sampleData.Length must be a multiple of 2."); 
     } 
     var histogram = new long[ushort.MaxValue + 1]; 
     if (safe) 
     { 
     for (int i = 0; i <= ushort.MaxValue; i++) 
     { 
      histogram[i] = 1; 
     } 
     } 
     for (int i = 0; i < sampleData.Length; i += 2) 
     { 
     histogram[(sampleData[i] << 8) | sampleData[i + 1]] += 1000; 
     } 
     return histogram; 
    } 

    public byte[] Encode(byte[] plainData) 
    { 
     if (plainData.Length % 2 != 0) 
     { 
     throw new ArgumentException("plainData.Length must be a multiple of 2."); 
     } 

     Int64 iBuffer = 0; 
     int iBufferCount = 0; 

     using (MemoryStream msEncodedOutput = new MemoryStream()) 
     { 
     //Write Final Output Size 1st 
     msEncodedOutput.Write(BitConverter.GetBytes(plainData.Length), 0, 4); 

     //Begin Writing Encoded Data Stream 
     iBuffer = 0; 
     iBufferCount = 0; 
     for (int i = 0; i < plainData.Length; i += 2) 
     { 
      Node FoundLeaf = HuffmanTree[(plainData[i] << 8) | plainData[i + 1]]; 

      //How many bits are we adding? 
      iBufferCount += FoundLeaf.CodeLength; 

      //Shift the buffer 
      iBuffer = (iBuffer << FoundLeaf.CodeLength) | FoundLeaf.Coded; 

      //Are there at least 8 bits in the buffer? 
      while (iBufferCount > 7) 
      { 
      //Write to output 
      int iBufferOutput = (int)(iBuffer >> (iBufferCount - 8)); 
      msEncodedOutput.WriteByte((byte)iBufferOutput); 
      iBufferCount = iBufferCount - 8; 
      iBufferOutput <<= iBufferCount; 
      iBuffer ^= iBufferOutput; 
      } 
     } 

     //Write remaining bits in buffer 
     if (iBufferCount > 0) 
     { 
      iBuffer = iBuffer << (8 - iBufferCount); 
      msEncodedOutput.WriteByte((byte)iBuffer); 
     } 
     return msEncodedOutput.ToArray(); 
     } 
    } 

    public byte[] Decode(byte[] bInput) 
    { 
     long iInputBuffer = 0; 
     int iBytesWritten = 0; 

     //Establish Output Buffer to write unencoded data to 
     byte[] bDecodedOutput = new byte[BitConverter.ToInt32(bInput, 0)]; 

     var current = HuffmanTree[HuffmanTree.Count - 1]; 

     //Begin Looping through Input and Decoding 
     iInputBuffer = 0; 
     for (int i = 4; i < bInput.Length; i++) 
     { 
     iInputBuffer = bInput[i]; 

     for (int bit = 0; bit < 8; bit++) 
     { 
      if ((iInputBuffer & 128) == 0) 
      { 
      current = current.Left; 
      } 
      else 
      { 
      current = current.Right; 
      } 
      if (current.IsLeaf) 
      { 
      bDecodedOutput[iBytesWritten++] = current.Uncoded1; 
      bDecodedOutput[iBytesWritten++] = current.Uncoded0; 
      if (iBytesWritten == bDecodedOutput.Length) 
      { 
       return bDecodedOutput; 
      } 
      current = HuffmanTree[HuffmanTree.Count - 1]; 
      } 
      iInputBuffer <<= 1; 
     } 
     } 
     throw new Exception(); 
    } 

    private static void EncodeTree(Node node, int depth, uint value) 
    { 
     if (node != null) 
     { 
     if (node.IsLeaf) 
     { 
      node.CodeLength = depth; 
      node.Coded = value; 
     } 
     else 
     { 
      depth++; 
      value <<= 1; 
      EncodeTree(node.Left, depth, value); 
      EncodeTree(node.Right, depth, value | 1); 
     } 
     } 
    } 

    private void BuildTree(long[] frequencies) 
    { 
     var tiny = 0.1/ushort.MaxValue; 
     var fraction = 0.0; 

     SortedDictionary<double, Node> trees = new SortedDictionary<double, Node>(); 
     for (int i = 0; i <= ushort.MaxValue; i++) 
     { 
     var leaf = new Node() 
     { 
      Uncoded1 = (byte)(i >> 8), 
      Uncoded0 = (byte)(i & 255), 
      Frequency = frequencies[i] 
     }; 
     HuffmanTree.Add(leaf); 
     if (leaf.Frequency > 0) 
     { 
      trees.Add(leaf.Frequency + (fraction += tiny), leaf); 
     } 
     } 

     while (trees.Count > 1) 
     { 
     var e = trees.GetEnumerator(); 
     e.MoveNext(); 
     var first = e.Current; 
     e.MoveNext(); 
     var second = e.Current; 

     //Join smallest two nodes 
     var NewParent = new Node(); 
     NewParent.Frequency = first.Value.Frequency + second.Value.Frequency; 
     NewParent.Left = first.Value; 
     NewParent.Right = second.Value; 

     HuffmanTree.Add(NewParent); 

     //Remove the two that just got joined into one 
     trees.Remove(first.Key); 
     trees.Remove(second.Key); 

     trees.Add(NewParent.Frequency + (fraction += tiny), NewParent); 
     } 
    } 

    } 

} 

使用示例:

要創建從樣本數據字典:

var freqs = Huffman16.GetFrequencies(File.ReadAllBytes(@"D:\nodes"), true); 

要與給定字典初始化編碼器:

var huff = new Huffman16(freqs); 

而做到一些壓縮:

var encoded = huff.Encode(raw); 

和解壓:

var raw = huff.Decode(encoded); 
+0

你的塊一直大小? – user57368 2009-07-02 03:11:57

+0

此外,我已經RAR了一個樣本數據文件,並確認數據是高度可壓縮的(〜9倍壓縮比)。 – Fantius 2009-07-02 03:16:11

+0

不,它們的大小並不一致。 – Fantius 2009-07-02 03:16:41

回答

3

我腦海中的難題在於如何構建靜態字典。您不想使用從您的示例數據構建的LZW詞典。 LZW浪費了大量的時間學習,因爲它不能比解壓縮器更快地創建字典(一個令牌只會在第二次被壓縮器看到時使用,所以解壓縮器可以在第一次看到它時將其添加到它的字典中) 。另一方面,它會向字典添加可能永遠不會使用的東西,以防字符串再次出現。 (例如,有一個'stackoverflow'的標記,你也會有'ac','ko','ve','rf'等等的條目......)

然而,看着原始的標記流從LZ77算法可以很好地工作。您只會看到至少看過兩次的字符串。然後,您可以創建一個包含在您的字典中的最常用的標記/字符串列表。

一旦你有一個靜態的字典,使用LZW SANS詞典更新似乎是一個容易實現,但讓我考慮靜態Huffman表代替了傳統的12位固定大小令牌的最佳壓縮(喬治·菲利普斯建議)。 LZW字典會爲所有可能永遠不會實際編碼的子字符串燒錄令牌(例如,如果您可以對'stackoverflow'進行編碼,將會有'st','sta','stac','stack','堆疊「等)。

在這一點上,它確實不是LZW - LZW的聰明之處在於解壓器如何構建壓縮器只能看到壓縮數據流的相同字典。你不會使用的東西。但是所有的LZW實現都有一個狀態,在這個狀態下字典已經滿了,不再更新,這就是你如何在靜態字典中使用它。

0
  1. 在正確的軌道是使用庫 - 幾乎每個現代的語言有一個壓縮庫。 C#,Python,Perl,Java,VB.net,無論你使用什麼。

  2. LZW通過依賴之前輸入的字典來節省一些空間。它有一個初始字典,當你解壓縮某些東西時,你將它們添加到字典 - 所以字典正在增長。 (我在這裏略去了一些細節,但這是總體思路)

您可以通過提供整個(完整)字典作爲初始字典來省略此步驟。但是這會花費一些空間。

3

LZW在解壓過程中添加到字典中,以確保它與壓縮器具有相同的字典狀態。否則解碼將無法正常工作。

但是,如果您處於字典固定的狀態,那麼您就不需要添加新代碼。

您的方法可以很好地工作,使用現有工具可以輕鬆地對原型和測量結果進行原型和測量。也就是說,壓縮示例文件,然後將示例和測試數據一起壓縮。後者的大小不如前者將是塊的預期壓縮大小。

LZW是一種聰明的方式,可以快速創建字典並提供體面的結果。但對典型數據塊的更徹底分析可能會生成更高效的字典。

LZW如何表示壓縮數據還有待改進。例如,根據每個字典參考的預期使用頻率,可以將霍夫曼編碼爲更接近最佳長度。爲了真正最佳,代碼應該被算術編碼。

1

我會看看你的數據,看看是否有一個明顯的原因,它很容易壓縮。你可能可以做比LZ78更簡單的事情。我已經完成了LZ77(回顧)和LZ78(字典)。

嘗試在您的數據上運行LZ77。沒有字典與LZ77,所以你可以使用一個圖書館沒有改變。 Deflate是LZ77的一個實現。

使用普通字典的想法很好,但很難知道這些文件是否彼此相似,或者只是自相似而不做一些測試。

0

我覺得這個問題對於重複的日誌條目和我想探索的東西來說非常有趣。

你可以分享使用這種方法的壓縮統計數據爲你的用例,所以我可以將它與其他替代方案進行比較嗎?

你有沒有考慮過普通字典會隨着時間的推移而增長或者這不是一個有效的選擇?