lz77优化_[原创]用哈希表优化的lz77压缩算法的实现

最近终于有空研究研究E*F的K*KYU2。

和预料到的一样仍然是广泛使用LZ77,而且是毫不改变地使用LZ77……但是,时代进步了,图片文件都是真彩色的了,大小变大了3倍,仍然使用LZ77的代价就是速度……大家都知道LZ77的特点就是解压超快,压缩巨慢(不然就不会有LZW这种不伦不类的算法出来了……)

在png的相关网站上查找了一下优化方案,自己写了一下优化代码,虽然目前速度仍然不能很让人满意(在双核的机器上压缩一个160多兆的文件用了60多秒),但是勉强可以用于实际应用了。贴出来和大家分享。如果要进一步优化,估计要玩一些trick,或是借助于指令级别的。

以下讨论是基于4096字节定长窗口的字典的LZ77,字节组织是每bit表示查表或是原数据;查表是2字节,前16位表示索引,后16位表示长度,因此最长的序列只能有15字节。优化的思路是哈希表。

由环境可以看出,低于3个字节的码序列是不需要查表保存的,因此码的最短长度是3个字节。使用一个哈希算法将之转化成1个字节的哈希码。这种哈希码共有256个。当在查表前,首先要计算出当前字节及后面两个字节组成的序列的哈希码,然后在哈希码表中查得第一条匹配记录。当然,即使哈希码匹配,3字节并不一定完全一样,这之后还是需要按字节比较。但这可以提前淘汰大部分的数据。匹配后,再查下一条记录。

优化的负担是额外增加了一个哈希码头指针表和一个链表。每一个字节在字典中的更新都会造成它本身及前面两个字节处共3个字节的哈希码变化,即表项删除和增加。为了使这种删除和增加更有效率,做成了双向链表的形式(实际测试下来比单向节省约一半时间)。当然这里的链表是以数组形式实现的,不需要动态分配空间,否则所带来的负担将是不可承受的。字典定义如下:

typedef

struct

_tagLz77Dict

{

inthash_start[256];

inthash_next[4096];

inthash_prev[4096];

chardict[4096];

}

lz77_dict,

*

p_lz77_dict;

其中,hash_start是256个哈希码的首码索引。hash_next是每个哈希码的下一个编码,hash_prev是每个哈希码的前一个编码。均以-1表示无效。

字典初始化函数如下:

void

init_dict(p_lz77_dict pdic)

{

inti;

    memset(pdic->dict,0,4096);

for(i=0; i<256;++i) pdic->hash_start[i]=–1;

    pdic->hash_start[HASH(0,0,0)]=4096–16;

for(i=0; i<4096;++i)…{

if(i<4096–16)…{

            pdic->hash_next[i]=–1;

            pdic->hash_prev[i]=–1;

        }else…{

            pdic->hash_next[i]=(i+1);

            pdic->hash_prev[i]=(i–1);

        }    }    pdic->hash_prev[0]=–1;

    pdic->hash_next[4095]=–1;

}

初始值仅后面16个码可用。

删除和增加哈希码函数:

void

remove_dict_pos(p_lz77_dict pdic,

int

pos)

{

chara, b, c;

    unsignedcharhash;

intold_pos=pos;

ints, last;

    a=pdic->dict[pos++];

    pos&=0xFFF;

    b=pdic->dict[pos++];

    pos&=0xFFF;

    c=pdic->dict[pos++];

    pos&=0xFFF;

    hash=HASH(a, b, c);

    last=pdic->hash_start[hash];

if(last==old_pos)…{

//head?last=pdic->hash_next[old_pos];

        pdic->hash_start[hash]=last;

if(last>=0)…{

            pdic->hash_prev[last]=–1;

        }

    }else…{

        last=pdic->hash_prev[old_pos];

        s=pdic->hash_next[old_pos];

if(last>=0)…{

            pdic->hash_next[last]=s;

        }

if(s>=0)…{

            pdic->hash_prev[s]=last;

        }        pdic->hash_prev[old_pos]=–1;

    }}

//

add a position’s hash rec

void

add_dict_pos(p_lz77_dict pdic,

int

pos)

{

chara, b, c;

    unsignedcharhash;

intold_pos=pos;

intlast;

    a=pdic->dict[pos++];

    pos&=0xFFF;

    b=pdic->dict[pos++];

    pos&=0xFFF;

    c=pdic->dict[pos++];

    pos&=0xFFF;

    hash=HASH(a, b, c);

    last=pdic->hash_start[hash];

    pdic->hash_start[hash]=old_pos;

if(last>=0) pdic->hash_prev[last]=old_pos;

    pdic->hash_next[old_pos]=last;

}

哈希算法随便选了一个(注意尽量不要全是3个字节异或,这样会使效率受影响,因为在实际应用中3个字节中有两个以上相同的比较多)

#define HASH(a, b, c)  (((unsigned char)a) + ((unsigned char)b) ^ ((unsigned char)c))

最后是解码函数主体:

//

dest_buf should allocate size+size/8 bytes at least

//

optimized algorithm

char

*

fast_lz77_encode(

const

char

*

buf,

int

size,

int

*

dest_size)

{

    nsg_auto_detect(__FUNCTION__);

char*dest=(char*)malloc(size+size/8+1);

char*org_dest=dest;

    lz77_dict dict;

intdict_start=4096–16;

intleft=size;

    unsignedcharbits=0;

intbitcount=8;

char*bits_pos=dest;

if(size==0)…{

        free(dest);

returnNULL;

    }    init_dict(&dict);

    dest++;

while(left>0)…{

intmax_len=0;

inttable_index=0;

inti;

if(left>=3)…{

            unsignedcharhash=HASH(buf[0], buf[1], buf[2]);

intoffset=dict.hash_start[hash];

if(offset>=0)…{

intlen=0;

while(offset>=0)…{

intpos=offset;

                    len=0;

while(len<15)…{

if(dict.dict[pos++]!=buf[len])break;

                        pos&=0xFFF;

++len;

                    }

if(len>max_len)…{

                        table_index=(offset–len)&0xFFF;

                        max_len=len;

                    }                    offset=dict.hash_next[offset];

                }            }        }

if(max_len>=3)…{

inti;

intpos=dict_start–2;

intold_pos;

*(unsignedshort*)dest=(table_index<<4)|max_len;

if(pos<0) pos+=4096;

            old_pos=pos;

//remove previous 2, next 2 dict

for(i=max_len+2; i>=0;—i)…{

                remove_dict_pos(&dict, pos++);

                pos&=0xFFF;

            }//update dict

for(i=0; i

                dict.dict[dict_start++]=buf[i];

                dict_start&=0xFFF;

            }            pos=old_pos;

for(i=max_len+2; i>=0;—i)…{

                add_dict_pos(&dict, pos++);

                pos&=0xFFF;

            }            dest+=2;

            buf+=max_len;

            left-=max_len;

        }else…{

intpos=dict_start–2;

inti;

/**//*bits |= 0;*//**//*not necessary*/if(pos<0) pos+=4096;

for(i=0; i<3;++i )…{

                remove_dict_pos(&dict, pos++);

                pos&=0xFFF;

            }            dict.dict[dict_start++]=*buf;

            dict_start&=0xFFF;

            pos-=3;

if(pos<0) pos+=4096;

for(i=0; i<3;++i )…{

                add_dict_pos(&dict, pos++);

                pos&=0xFFF;

            }*dest++=*buf++;

—left;

        }

if(—bitcount==0)…{

//fullbitcount=8;

*bits_pos=bits;

            bits_pos=dest++;

            bits=0;

        }        bits>>=1;

    }*bits_pos=bits>>bitcount;

*dest_size=dest–org_dest;

returnorg_dest;

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值