2010-02-10 42 views
10

Tôi đã implementated a simple compressor sử dụng mã Huffman tinh khiết dưới Windows.But Tôi không biết nhiều về làm thế nào để giải mã các tập tin nén một cách nhanh chóng, thuật toán xấu của tôi là:Làm thế nào để giải mã mã huffman một cách nhanh chóng?

liệt kê tất cả các mã Huffman trong bảng mã sau đó so sánh nó với các bit trong tập tin nén. Nó quay ra kết quả khủng khiếp: giải nén tập tin 3MB sẽ cần 6 giờ.

Bạn có thể cung cấp thuật toán hiệu quả hơn không? Tôi có nên sử dụng hàm băm hay gì đó không?

Cập nhật: Tôi đã implementated the decoder với bảng trạng thái, dựa trên người bạn của tôi Lin của advice.I nghĩ rằng phương pháp này sẽ tốt hơn so với cây travesal Huffman, 3MB trong vòng 6s.

cảm ơn.

+4

Xin chúc mừng cho mã được định dạng tốt của bạn. Làm cho mã WinAPI gần như ngon miệng. – Manuel

+1

FYI: bộ giải mã hiện đại có thể làm tốc độ giải nén ~ 1GB/giây (hoặc hơn) trên máy tính trung bình hiện tại: bạn phải giới hạn độ dài của mã huffman đến 11/12, sử dụng bảng giải nén và sử dụng nhiều luồng huffman, vì vậy phụ thuộc dữ liệu được giảm thiểu. – geza

Trả lời

16

Một cách để tối ưu hóa các phương pháp nhị phân-cây là sử dụng một bảng tra cứu. Bạn sắp xếp bảng để bạn có thể tra cứu trực tiếp một mẫu bit được mã hóa cụ thể, cho phép chiều rộng bit tối đa có thể có của bất kỳ mã nào.

Vì hầu hết các mã không sử dụng chiều rộng tối đa đầy đủ, chúng được bao gồm tại nhiều vị trí trong bảng - một vị trí cho mỗi kết hợp của các bit chưa sử dụng. Bảng cho biết số lượng bit cần loại bỏ từ đầu vào cũng như đầu ra đã giải mã.

Nếu mã dài nhất quá dài, vì vậy bảng không thực tế, thỏa hiệp là sử dụng cây tra cứu độ rộng cố định nhỏ hơn. Ví dụ, bạn có thể sử dụng một bảng 256-mục để xử lý một byte. Nếu mã đầu vào lớn hơn 8 bit, mục bảng cho biết rằng việc giải mã không đầy đủ và đưa bạn đến một bảng xử lý 8 bit tiếp theo. Bảng lớn hơn bộ nhớ thương mại cho tốc độ - 256 mục có lẽ là quá nhỏ.

Tôi tin rằng cách tiếp cận chung này được gọi là "bảng tiền tố" và là những gì mã BobMcGees trích dẫn đang thực hiện. Một sự khác biệt có thể là một số thuật toán nén yêu cầu bảng tiền tố phải được cập nhật trong khi giải nén - điều này không cần thiết cho Huffman đơn giản.IIRC, lần đầu tiên tôi nhìn thấy nó trong một cuốn sách về các định dạng tập tin đồ họa bitmap mà bao gồm GIF, một thời gian trước khi hoảng sợ bằng sáng chế.

Cần dễ dàng tính toán trước bảng tra cứu đầy đủ, số tương đương có thể bắt đầu hoặc bảng cây nhỏ từ mô hình cây nhị phân. Cây nhị phân vẫn là biểu diễn chính của mã - bảng tra cứu này chỉ là tối ưu hóa.

+0

+1 Đây là phương pháp tôi sử dụng trong bộ giải mã JPEG, tôi xây dựng bảng tra cứu trực tiếp từ phân đoạn DHT trong hình ảnh, không cần sự phức tạp của cây nhị phân. – matja

0

Tại sao không sử dụng thuật toán giải nén trong cùng một mô-đun nguồn? Nó có vẻ là một thuật toán khá.

+3

Đó không phải là giải nén mà OP nói sẽ mất 6 giờ để giải nén tập tin 3MB? –

4

Cách điển hình để giải nén mã Huffman là sử dụng cây nhị phân. Bạn chèn mã của bạn trong cây, để mỗi bit trong một mã đại diện cho một chi nhánh hoặc sang trái (0) hoặc phải (1), với byte được giải mã (hoặc bất kỳ giá trị nào bạn có) trong lá.

Giải mã sau đó chỉ là trường hợp đọc bit từ nội dung được mã hóa, đi bộ trên cây cho từng bit. Khi bạn tới một chiếc lá, phát ra giá trị đã giải mã đó và tiếp tục đọc cho đến khi đầu vào bị cạn kiệt.

Cập nhật:this page mô tả kỹ thuật và có đồ họa lạ mắt.

+4

Cấu trúc liên kết là hữu ích nhất để xây dựng cây, nhưng tôi nghĩ rằng một mảng sẽ là tốt nhất để thực hiện tra cứu. Nó không phải là hoàn toàn dân cư, nhưng 16-bit phím vào một mảng nút 65536 nên thực hiện tốt. NB: Tôi chưa bao giờ viết một giải nén. – Potatoswatter

+0

@Potatoswatter: Vì vậy, làm thế nào để bạn đi từ một bộ đệm giữ chiều dài khác nhau (ở mức bit) Huffman mã, để một chỉ mục mảng? Điểm của phương pháp dựa trên cây là nó cho bạn biết khi nào nên dừng đọc bit. Có lẽ tôi chỉ thiếu một cái gì đó, nó đã được một thời gian kể từ khi tôi đã viết một bộ giải mã Huffman. – unwind

+0

@unwind: bạn có thể đọc nhiều bit cùng lúc và sử dụng bảng tra cứu chứa các nút nhánh hoặc nút lá. Các nút nhánh cho một con trỏ tới bảng cho một vài bit tiếp theo. Các nút lá cung cấp giá trị đã giải mã và độ dài thực tế để bạn biết số lượng bit bạn đọc thêm. Nếu có độ dài tối đa hợp lý (nói 16 bit hoặc ít hơn) thì bạn có thể sử dụng một bảng các nút lá như Potatoswatter gợi ý. –

5

Tại sao không xem xét cách thức GZIP source thực hiện điều đó, cụ thể là mã giải nén Huffman trong unpack.c cụ thể? Nó làm chính xác những gì bạn đang có, ngoại trừ nó làm nó nhiều, nhanh hơn nhiều.

Từ những gì tôi có thể biết, nó sử dụng mảng tra cứu và hoạt động thay đổi/mặt nạ hoạt động trên toàn bộ từ để chạy nhanh hơn. Mặc dù mã khá dày đặc.

EDIT: đây là nguồn gốc hoàn toàn

/* unpack.c -- decompress files in pack format. 
* Copyright (C) 1992-1993 Jean-loup Gailly 
* This is free software; you can redistribute it and/or modify it under the 
* terms of the GNU General Public License, see the file COPYING. 
*/ 

#ifdef RCSID 
static char rcsid[] = "$Id: unpack.c,v 1.4 1993/06/11 19:25:36 jloup Exp $"; 
#endif 

#include "tailor.h" 
#include "gzip.h" 
#include "crypt.h" 

#define MIN(a,b) ((a) <= (b) ? (a) : (b)) 
/* The arguments must not have side effects. */ 

#define MAX_BITLEN 25 
/* Maximum length of Huffman codes. (Minor modifications to the code 
* would be needed to support 32 bits codes, but pack never generates 
* more than 24 bits anyway.) 
*/ 

#define LITERALS 256 
/* Number of literals, excluding the End of Block (EOB) code */ 

#define MAX_PEEK 12 
/* Maximum number of 'peek' bits used to optimize traversal of the 
* Huffman tree. 
*/ 

local ulg orig_len;  /* original uncompressed length */ 
local int max_len;  /* maximum bit length of Huffman codes */ 

local uch literal[LITERALS]; 
/* The literal bytes present in the Huffman tree. The EOB code is not 
* represented. 
*/ 

local int lit_base[MAX_BITLEN+1]; 
/* All literals of a given bit length are contiguous in literal[] and 
* have contiguous codes. literal[code+lit_base[len]] is the literal 
* for a code of len bits. 
*/ 

local int leaves [MAX_BITLEN+1]; /* Number of leaves for each bit length */ 
local int parents[MAX_BITLEN+1]; /* Number of parents for each bit length */ 

local int peek_bits; /* Number of peek bits currently used */ 

/* local uch prefix_len[1 << MAX_PEEK]; */ 
#define prefix_len outbuf 
/* For each bit pattern b of peek_bits bits, prefix_len[b] is the length 
* of the Huffman code starting with a prefix of b (upper bits), or 0 
* if all codes of prefix b have more than peek_bits bits. It is not 
* necessary to have a huge table (large MAX_PEEK) because most of the 
* codes encountered in the input stream are short codes (by construction). 
* So for most codes a single lookup will be necessary. 
*/ 
#if (1<<MAX_PEEK) > OUTBUFSIZ 
    error cannot overlay prefix_len and outbuf 
#endif 

local ulg bitbuf; 
/* Bits are added on the low part of bitbuf and read from the high part. */ 

local int valid;     /* number of valid bits in bitbuf */ 
/* all bits above the last valid bit are always zero */ 

/* Set code to the next 'bits' input bits without skipping them. code 
* must be the name of a simple variable and bits must not have side effects. 
* IN assertions: bits <= 25 (so that we still have room for an extra byte 
* when valid is only 24), and mask = (1<<bits)-1. 
*/ 
#define look_bits(code,bits,mask) \ 
{ \ 
    while (valid < (bits)) bitbuf = (bitbuf<<8) | (ulg)get_byte(), valid += 8; \ 
    code = (bitbuf >> (valid-(bits))) & (mask); \ 
} 

/* Skip the given number of bits (after having peeked at them): */ 
#define skip_bits(bits) (valid -= (bits)) 

#define clear_bitbuf() (valid = 0, bitbuf = 0) 

/* Local functions */ 

local void read_tree OF((void)); 
local void build_tree OF((void)); 

/* =========================================================================== 
* Read the Huffman tree. 
*/ 
local void read_tree() 
{ 
    int len; /* bit length */ 
    int base; /* base offset for a sequence of leaves */ 
    int n; 

    /* Read the original input size, MSB first */ 
    orig_len = 0; 
    for (n = 1; n <= 4; n++) orig_len = (orig_len << 8) | (ulg)get_byte(); 

    max_len = (int)get_byte(); /* maximum bit length of Huffman codes */ 
    if (max_len > MAX_BITLEN) { 
    error("invalid compressed data -- Huffman code > 32 bits"); 
    } 

    /* Get the number of leaves at each bit length */ 
    n = 0; 
    for (len = 1; len <= max_len; len++) { 
    leaves[len] = (int)get_byte(); 
    n += leaves[len]; 
    } 
    if (n > LITERALS) { 
    error("too many leaves in Huffman tree"); 
    } 
    Trace((stderr, "orig_len %ld, max_len %d, leaves %d\n", 
     orig_len, max_len, n)); 
    /* There are at least 2 and at most 256 leaves of length max_len. 
    * (Pack arbitrarily rejects empty files and files consisting of 
    * a single byte even repeated.) To fit the last leaf count in a 
    * byte, it is offset by 2. However, the last literal is the EOB 
    * code, and is not transmitted explicitly in the tree, so we must 
    * adjust here by one only. 
    */ 
    leaves[max_len]++; 

    /* Now read the leaves themselves */ 
    base = 0; 
    for (len = 1; len <= max_len; len++) { 
    /* Remember where the literals of this length start in literal[] : */ 
    lit_base[len] = base; 
    /* And read the literals: */ 
    for (n = leaves[len]; n > 0; n--) { 
     literal[base++] = (uch)get_byte(); 
    } 
    } 
    leaves[max_len]++; /* Now include the EOB code in the Huffman tree */ 
} 

/* =========================================================================== 
* Build the Huffman tree and the prefix table. 
*/ 
local void build_tree() 
{ 
    int nodes = 0; /* number of nodes (parents+leaves) at current bit length */ 
    int len;  /* current bit length */ 
    uch *prefixp; /* pointer in prefix_len */ 

    for (len = max_len; len >= 1; len--) { 
    /* The number of parent nodes at this level is half the total 
    * number of nodes at parent level: 
    */ 
    nodes >>= 1; 
    parents[len] = nodes; 
    /* Update lit_base by the appropriate bias to skip the parent nodes 
    * (which are not represented in the literal array): 
    */ 
    lit_base[len] -= nodes; 
    /* Restore nodes to be parents+leaves: */ 
    nodes += leaves[len]; 
    } 
    /* Construct the prefix table, from shortest leaves to longest ones. 
    * The shortest code is all ones, so we start at the end of the table. 
    */ 
    peek_bits = MIN(max_len, MAX_PEEK); 
    prefixp = &prefix_len[1<<peek_bits]; 
    for (len = 1; len <= peek_bits; len++) { 
    int prefixes = leaves[len] << (peek_bits-len); /* may be 0 */ 
    while (prefixes--) *--prefixp = (uch)len; 
    } 
    /* The length of all other codes is unknown: */ 
    while (prefixp > prefix_len) *--prefixp = 0; 
} 

/* =========================================================================== 
* Unpack in to out. This routine does not support the old pack format 
* with magic header \037\037. 
* 
* IN assertions: the buffer inbuf contains already the beginning of 
* the compressed data, from offsets inptr to insize-1 included. 
* The magic header has already been checked. The output buffer is cleared. 
*/ 
int unpack(in, out) 
    int in, out;   /* input and output file descriptors */ 
{ 
    int len;    /* Bit length of current code */ 
    unsigned eob;   /* End Of Block code */ 
    register unsigned peek; /* lookahead bits */ 
    unsigned peek_mask;  /* Mask for peek_bits bits */ 

    ifd = in; 
    ofd = out; 

    read_tree();  /* Read the Huffman tree */ 
    build_tree(); /* Build the prefix table */ 
    clear_bitbuf(); /* Initialize bit input */ 
    peek_mask = (1<<peek_bits)-1; 

    /* The eob code is the largest code among all leaves of maximal length: */ 
    eob = leaves[max_len]-1; 
    Trace((stderr, "eob %d %x\n", max_len, eob)); 

    /* Decode the input data: */ 
    for (;;) { 
    /* Since eob is the longest code and not shorter than max_len, 
     * we can peek at max_len bits without having the risk of reading 
     * beyond the end of file. 
    */ 
    look_bits(peek, peek_bits, peek_mask); 
    len = prefix_len[peek]; 
    if (len > 0) { 
     peek >>= peek_bits - len; /* discard the extra bits */ 
    } else { 
     /* Code of more than peek_bits bits, we must traverse the tree */ 
     ulg mask = peek_mask; 
     len = peek_bits; 
     do { 
       len++, mask = (mask<<1)+1; 
     look_bits(peek, len, mask); 
     } while (peek < (unsigned)parents[len]); 
     /* loop as long as peek is a parent node */ 
    } 
    /* At this point, peek is the next complete code, of len bits */ 
    if (peek == eob && len == max_len) break; /* end of file? */ 
    put_ubyte(literal[peek+lit_base[len]]); 
    Tracev((stderr,"%02d %04x %c\n", len, peek, 
     literal[peek+lit_base[len]])); 
    skip_bits(len); 
    } /* for (;;) */ 

    flush_window(); 
    Trace((stderr, "bytes_out %ld\n", bytes_out)); 
    if (orig_len != (ulg)bytes_out) { 
    error("invalid compressed data--length error"); 
    } 
    return OK; 
} 
1

Bạn có thể thực hiện một loại tra cứu hàng loạt trên tra cứu cây Huffmann thông thường:

  1. Chọn độ sâu bit (gọi nó là sâu n); đây là một sự cân bằng giữa tốc độ, bộ nhớ và thời gian đầu tư để xây dựng các bảng;
  2. Tạo bảng tra cứu cho tất cả 2^n chuỗi bit có độ dài n. Mỗi mục có thể mã hóa một số mã thông báo hoàn chỉnh; thường sẽ có một số bit còn sót lại chỉ là tiền tố của mã Huffman: đối với mỗi mã này, tạo liên kết đến bảng tra cứu thêm cho mã đó;
  3. Tạo các bảng tra cứu khác. Tổng số bảng tối đa là một số ít hơn số lượng các mục được mã hóa trong cây Huffmann.

Chọn độ sâu là bội số của bốn, ví dụ: độ sâu 8, phù hợp cho hoạt động dịch chuyển bit.

Postscript Điều này khác với ý tưởng trong bình luận potatoswatter về câu trả lời Thư giãn và từ câu trả lời Steve314 trong việc sử dụng nhiều bảng: điều này có nghĩa rằng tất cả các tra cứu n -bit được đưa vào sử dụng, vì vậy cần được nhanh hơn nhưng làm cho bảng xây dựng và tra cứu đáng kể phức tạp hơn, và sẽ tiêu thụ nhiều không gian hơn cho một chiều sâu nhất định.

Các vấn đề liên quan