以前学习了一下GIF的LZW算法,不过只是学习了一下(见我以前的那篇博文《LZW for GIF算法原理和实现》),没有实践,也没有看看效果到底怎么样,因为现在ZIP库很多,基本上不需要自己写压缩算法了,LZW的压缩效果也比不上它们。不过最近有个嵌入式系统上的数据记录需求,希望把运行过程中采集的数据都记录下来,但是存储空间比较有限,处理器的能力也比较有限,是266兆的PPC,所以不能使用复杂的算法了,于是又想起LZW算法来,首先是采集的数据具有短时间内大量重复的特点,可以用简单的算法就达到很好的效果,其次这个算法处理得好的话很省资源,至于压缩比则是其次的考虑。

  为了验证一下算法的正确性和效果,先用python实现了一下算法。由于只是验证算法,所以不考虑效率,只追求编程简单。用Python实现起来还真是方便,如果不进行位紧缩的话,二十多行代码就搞定了,拿几个文件试了下压缩和解压,一个140多K的cpp程序,可以压到33K多,用一个1兆多的软盘映像文件,可以压到一半左右,拿txt的《三国演义》,则只有70%了(不过rar也只能压到50%多)。全部文件解压出来和原文件二进制比较完全一致,证明了算法的正确性。

  实践表明选用的最大位宽越大压缩率越高,但是内存开销和时间开销都最大,而且由于最大位宽比较大,进行位紧缩后效果不明显,而14位的最大位宽综合效果最理想。如果是在嵌入式环境下使用的话,使用小一点的最大位宽开销要小一些,不过不要小于12位。

  有了这个Python版本,接下来准备用C语言再实现一遍的时候就有参考了。

# -*- coding: gbk -*-
def LZW ( inStr,narrow=False,bits=14):
    '''使用LZW压缩算法压缩。
        narrow为True时输出字节流位紧缩
        默认最大位宽14位,允许范围12~16位'''
    if isinstance(inStr,str):
        inStr=list(inStr)
        for i in range(len(inStr)):
            inStr[i]=ord(inStr[i])
    sOutStr=[256]   #先放一个 开始&清除 标记
    mTagMap={}      #字典
    iTagCurrent=258  #当前的标记总数 0~255 标记256 为begin & clear , 257 为end
    iBitsLen=9

    iTag=inStr[0]   #当前标记
    ii=0
    cTemp=0
    if bits>16 or bits<12:
        return None
    iMaxLen=(1<<bits)-1     #最多允许的标记数,由位宽决定
    
    for ii in range(1,len(inStr)):
        cChar=inStr[ii]
        cTemp=(iTag<<8)+cChar   #(前缀 后缀)
        if cTemp in mTagMap:    #该(前缀 后缀)已存在
            iTag=mTagMap[cTemp] #取出其标记
        else:   #不存在
            sOutStr.append(iTag) #将前缀放入输出流
            mTagMap[cTemp]=iTagCurrent
            iTagCurrent+=1      #增加一个标记,并放入字典
            iTag=cChar  #当前标记为后缀

        if iTagCurrent>=iMaxLen:    #如果到达了最大标记数,清除字典,从头开始
            sOutStr.append(256)
            mTagMap={}
            iTagCurrent=258

    if iTag!=0:
        sOutStr.append(iTag)
    sOutStr.append(257) #放入结束标记
    
    if narrow:  #位紧缩
        return Narrow(sOutStr)
    else:
        return sOutStr
    
def Narrow (sOutStr):
    sOutN=[]
    iTemp=0
    BitLeft=0 
    nowBit=9 #当前位宽
    nowStag=1<<nowBit #当前位宽允许的标记数
    nowTagCount=258
    for cChar in sOutStr:
        iTemp=iTemp+(cChar<<BitLeft)
        nowTagCount+=1
        BitLeft+=nowBit
        
        if cChar==256:
            nowBit=9
            nowStag=1<<nowBit
            nowTagCount=258
        
        if nowTagCount>=nowStag:
            nowBit+=1
            nowStag=1<<nowBit
            
        while BitLeft>=8:
            sOutN.append(iTemp&0xff)
            iTemp=iTemp>>8
            BitLeft-=8
    if BitLeft>0:
        sOutN.append(iTemp)
    return sOutN

def UnNarrow (inStr):
    sOut=[]
    iTemp=0
    BitLeft=0
    nowBit=9
    nowStag=1<<nowBit
    mask=nowStag-1
    nowTagCount=258
    for cChar in inStr:
        iTemp=iTemp+(cChar<<BitLeft)
        BitLeft+=8
        if BitLeft>=nowBit:
            cTemp=iTemp&mask
            iTemp=iTemp>>nowBit
            BitLeft-=nowBit
            sOut.append(cTemp)
            nowTagCount+=1
            if nowTagCount>=nowStag:
                nowBit+=1
                nowStag=1<<nowBit
                mask=nowStag-1
            if cTemp==256:
                nowBit=9
                nowStag=1<<nowBit
                mask=nowStag-1
                nowTagCount=258
    if BitLeft>0:
        sOut.append(iTemp)

    return sOut

def deTag ( mTagMap,nowTag,outStr):
    '''将一个标记转化为元字符序列,并放入到输出流中'''
    if nowTag>=0:
        sTemp=[]
        while nowTag>255:
            pair=mTagMap[nowTag]
            sTemp.append(pair[1])
            nowTag=pair[0]
        sTemp.append(nowTag)
        sTemp.reverse()
        outStr+=sTemp
    return nowTag

def UnLZW ( inStr , narrow=False):
    if narrow:
        inStr=UnNarrow(inStr)
    
    mTagMap={}
    outStr=[]
    nowTagCount=258
    sTemp=[]
    nowTag=-1
    for cChar in inStr:
        if cChar==256:
            if nowTag>=0:
                deTag(mTagMap,nowTag,outStr)
            mTagMap={}
            nowTagCount=258
            nowTag=-1
        elif cChar==257:
            if nowTag>=0:
                deTag(mTagMap,nowTag,outStr)
                nowTag=-1
            return outStr
        elif nowTag==-1: #刚开始
            nowTag=cChar
        else:
            pair=[nowTag,0]
            mTagMap[nowTagCount]=pair
            nowTagCount+=1
            surfix=cChar
            while surfix>255:
                if surfix not in mTagMap:
                    print 'Thera are errors in input'
                    return outStr
                surfix=mTagMap[surfix][0]
            pair[1]=surfix
            deTag(mTagMap,nowTag,outStr)
            nowTag=cChar
            
def fileLZW ( inFile , outFile , bit=14 ):
    import os
    fsize=os.stat(inFile)[6]
    f=os.open(inFile,os.O_BINARY)
    s=os.read(f,fsize)
    os.close(f)
    
    a=LZW(s,True,bit)
    f=open(outFile,'wb')
    outs=''
    for i in a:
        outs+=chr(i)
    f.write(outs)
    f.close()

def fileUnlzw ( inFile , outFile):
    import os
    fsize=os.stat(inFile)[6]
    f=os.open(inFile,os.O_BINARY)
    s=os.read(f,fsize)
    a=[]
    for i in s:
        a.append(ord(i))
    b=UnLZW(a,True)
    f=open(outFile,'wb')
    outs=''
    for i in b:
        outs+=chr(i)
    f.write(outs)
    f.close()