LZW解压缩C++实现

压缩方法:
#include "hashChain.h"
#include 
#include 
//包含一些c语言字符串处理的函数
#include 
#include 
//定义一些全局变量

const int DIVISOR   = 4099,    //hash表的取余的那个数 
          MAX_CODES = 4096,    //2^12,用来表示前缀的编码的,用12位去表示这个前缀编码
          BYTE_SIZE = 8,       //因为用12位去表示编码,但是
          EXCESS    = 4,       //因为用
          ALPHA     = 256,     //2^BYTE_SIZE
          MASK1     = 255,     //ALPHA - 1
          MASK2     = 15;      //2^EXCESS - 1

//hash表的一个节点
//pair.first = key,pair.second = code
typedef std::pair<const long,int> pairType;


int leftOver;
bool bitsLeftOver = false;//上来表示剩余的4位的,所以我们需要输出8位,然后留下4位,这样bitsLeftOver就为true了,所以这样
//就可以交换的输出和不输出4位了
std::ifstream in;    //表示输入文件流,其实就是读文件的流
std::ofstream out;  //表示用来输出的文件流,其实就是写文件的流

//处理用户输入的数据
//这个是用C风格写的,我注释的是用C++风格写的
void setFiles(int argc,char *argv[]){
    /*
     *std::string inputFlie,outputFile;
     *if(argc >= 2)
        inputFile+=argv[1];
      else{
        std::cout<<"Enter name of file to compress\n";
        std::cin>>inputFile;
      }
      in.open(inputFile,std::ios::binary);
      if(in.fail()){
        std::cerr<<"Cannot open "<
    char inputFile[54],outputFile[50];
    if(argc >= 2){//先判断下用户输入的参数的个数
        //将文件名copy到inputfile中去
        //extern char *strcpy (char *__restrict __dest, const char *__restrict __src)
        strcpy(inputFile,argv[1]);
    }else{//如果用户没有输入要转换的文件,
        std::cout<<"Enter name of file to compress\n";
        std::cin>>inputFile;
    }
    //用二进制文件方式打开
    in.open(inputFile,std::ios::binary);
    if(in.fail()){//如果文件打开失败的话,
        std::cerr<<"Cannot open "<<inputFile<<std::endl;
        exit(1);
    }
    //如果能够打开文件
    strcpy(outputFile,inputFile);
    //拼接函数其实
    strcat(outputFile,".zzz");
    out.open(outputFile,std::ios::binary);
}
/*output()函数
 * 作用:输出代码p
 *首先分下一下,因为pcode(也就是代码,其实就是编码)是十二位的,然后两个pcode就可以构成二十四位了,所以我们第一次输出
 *pcode的8位,然后剩下的四位,与下一次要输出的十二位合起来一起输出。这个时候就要注意,我们第一次要输出的8位是那几位了。
 *因为,我们习惯的数字格式都是从左到右表示的是从高位到低位,所以为了表示这个规则,那么我们第一次输出的是pcode的高8位。
 *然后剩下低四位,留在下一次输出。其实就是下面的程序的else部分:
 *          leftOver = int(pcode & MASK2); //MASK2 = 1111(也就是15),这样得到的就是低四位,留在后面输出
 *          bitsLeftOver = true;//用来表示下一次要输出低四位
 *          c = int(pcode >> EXCESS);//其中EXCESS表示的是4,这样其实就是将低四位移除去,把高8位输出
 *          out.put(c);//写到文件中去,也就是输出了
 *(2)如果是本次要输出上一次留下的低四位的情况:
 *那么还是根据从左到右的原则,因为上一次没输出的四位,是在本次要输出的十二位的前面(也就是左面),所以要先输出上一次的低四位
 *然后还是根据从左到右的原则,那么第一次输出的8位肯定是上一次的低四位,然后加上这一次的最高的四位来组合输出,然后剩下这一次
 *代码的低八位,输出。也就是下面代码中的if部分
 *          if(bitsLeftOver){//如果存在要输出的四位的话
 *              d = int(pcode & MASK1);//其中MASK1是1111 1111 也就是255,这样可以得到低8位,也就是第二个要输出的字节
 *              c = int((leftOver << EXCESS ) | (pcode >> BYTE_SIZE));//这个运算是将leftOver变成高四位,然后把pcode的高四位
 *              //取出来加到leftOver的后边,其中pcode >> BYTE_SIZE//就是右移8位得到高四位
 *              out.put(c);//先输出上一次的低四位跟本次的高四位组合成的字节
 *              out.put(d);//接着输出这一次的低8位
 *              bitsLeftOver = false;//下一次不需要输出剩余的四位了
 *          }
 */
void output(long pcode){
    int c,d;
    if(bitsLeftOver){//如果上次没输出最高的4位的话,这次输出
        //MASK1 is 1111 1111
        d = int(pcode & MASK1);//得到低8位的值,作为一个字节输出
                    //leftOver << EXCESS 使第四位变成了高四位,而pcode>>BYTE_SIZE使第9---12位变成了1--4,也就是变成了低四位了
                    //通过 | 运算,其实就是 高四位 0000 | 0000 低四位,其实就是相加了
        c = int((leftOver << EXCESS)|(pcode>>BYTE_SIZE));
        out.put(c);
        out.put(d);
        bitsLeftOver = false;
    }else{//如果前边没有剩余的4位的话,也就是说我需要留出4位来等着下一次输出
        //等待下一次输出的时候,因为这个pcode肯定是一个代码,也就是12位的代码,所以每一次只需要输出的是代码就好了
        //因为数字的高位是从左到右的,所以我们输出的时候也要遵循这个原则,那么我先输出的是左边的8位,所以c=pcode >> EXCESS;
        //然后剩余的低四位作为下一次输出的最高四位输出就好了,这样还是凑齐了12位,还是从左到右(按着从高位到地位的位置来的)
        leftOver = pcode & MASK2;//低四位作为了下一次要输出的
        bitsLeftOver = true;
        //课本中的是
        c = int(pcode >> EXCESS);//输出高八位
        out.put(c);
    }
}
/*压缩算法:
 *  其实很简单,它用一个map存储,其中value表示的是关键字对应的数值是多少,也可以理解为代码,也就是p的值
 *  key表示的是关键字,也就是p+c。
 *  所以关键字是由两部分组成的,一部分p是一个代码(表示的是前边的某一个关键字),另一部分c,表示的当前读入的一个
 *  字母。所以这个时候我们在去压缩的时候,就可以这样操作了
 *      (1)首先开始令p = c(第一个读入的数据),其肯定在表中(因为表的初始化了所有的单个字母(也就是ASIIC码))
 *      (2)然后继续读取下一个=字母c。这个时候就要判断了p+c这个关键字是不是在表中,如果在表中,那么输出代码p,
 *      然后将p+c表示的关键字放入表中(前提是表中还有位置)。然后令p = c继续下一次的读取操作
 *      (3)如果p+c表示的关键字在表中,那么我们就令p 等于 p+c对应的value的值(也就是这个p+c关键字对应的代码是多少,
 *      因为p必定是一个代码,也就是其关键字对应的值),然后继续读取
 *      (4)重复上面的步骤,直至读取完所有的字符
 *  注意,这里有一个小细节,如何表示p+c这个关键字呢,首先分析下,我们这里令p是一个12位的数(也就是最大是2^12-1)。
 *  然后c表示的是一个字符,也就是8位,所以我们让p的12位都在c的8位之前,那么就可以唯一的表示这个关键字了,也就是
 *              theKey = p<<8 + c;
 *
 */

void compress(){

    hashChain<long,int> h(DIVISOR);//创建一个hash表用来存储关系对
    //这个表示的是录入的初始化的字典
    for(int i = 0; i < ALPHA;++i)
        h.insert(pairType(i,i));
    int codeUsed = ALPHA;//这个表示已经被使用的编码号有多少个,最大有MAX_CODE

    int c = in.get();//这个表示的是当前的字符
    if(c != EOF){//如果没有到达文件尾的
        long pcode = c;//表示当前的前缀
        while((c = in.get()) != EOF){//如果不是空的话
            //pcode左移8位表示的是相当于是高于c的八位了]
            //代码左移8位,让给c字符一个8位的位置
            long theKey = (pcode << BYTE_SIZE) + c;
            //theKey = pcode + c
            //去在字典里查找pcode + c
            pairType  *thePair = h.find(theKey);
            if(thePair == NULL){//如果字典不存在的话,那么就输出pcode,并且插入pcode+c
                output(pcode);
                if(codeUsed < MAX_CODES)//如果编码还没占满的话,那么就插入
                    //书中的是
                    //hash.insert(pairType((pcode << BYTE_SIZE) | c,codeUsed++))
                    h.insert(pairType((pcode << BYTE_SIZE) | c,codeUsed++));
                pcode = c;
            }else//如果已经存在了的话,那么我就用这个thekey的编号来代替它了
                pcode = thePair->second;
        }
        output(pcode);
        if(bitsLeftOver)
            out.put(leftOver << EXCESS);
    }
    out.close();
    in.close();
}
//运行此程序的时候  ./compress.out  filename
//其中compress.out表示的是此程序的可执行代码,filename表示的是要压缩的文件
int main(int argc,char *argv[])
{
    setFiles(argc,argv);
    compress();
    return 0;
}
解压方法:
#include "sortedChain.h"
#include 
#include 
#include 
#include 

const int MAX_CODES = 4096,     //表示的是12位的代码(也就是P)
          BYTESIZE  = 8,        //一个字节的大小
          EXCESS    = 4,        //表示移动4位的位置
          ALPHA     = 256,      //表示一个8位ASCII码的全部表示的代码
          MASK      = 15;       //表示低4位或者高四位的数据


std::ifstream in;    //表示的是读取的文件
std::ofstream out;   //表示的是输出的文件

typedef std::pair<int,char> pairType;//注意,pair存放的是也就是表示的是存放的是它的前缀码跟它的字符,也就是说
//前缀加它的字符,表示的是要输出的字符是什么

//全局变量
pairType ht[MAX_CODES];     //表示要翻译的代码的字典
char s[MAX_CODES];          //用来盛放一个代码对应的字符是什么
int size;                   //表示当前一个代码输出多少个字符,可以用来保留本次代码的第一个字符
int leftOver;               //是不是有要输出的四位
bool bitsLeftOver = false;  //

void setFiles(int argc,char *argv[]){
    char outputFile[50],inputFile[54];
    if(argc == 2)
        //strcpy(des,res),将第二个数组的内容拷贝到第一个数组中去
        std::strcpy(outputFile,argv[1]);
    else{
        std::cout<<"Enter name of file to decompress"<<std::endl;
        std::cout<<"Omit the extension .zzz"<<std::endl;
        std::cin>>outputFile;
    }
    std::strcpy(inputFile,outputFile);
    std::strcat(inputFile,".zzz");
    in.open(inputFile,std::ios::binary);
    if(in.fail()){
        std::cerr<<"Cannot open "<<inputFile<<std::endl;
        exit(1);
    }
    out.open(outputFile,std::ios::binary);
}

bool getCode(int &code);
void output(int code);

void decompress(){
    int codesUsed = ALPHA;
    int pcode,  //previous code
        ccode;  //current code
    if(getCode(pcode)){
        s[0] = pcode;//表示第一个元素
        out.put(s[0]);//直接输出
        size = 0;
        while(getCode(ccode)){
            //注意先output的话,我的s[size]表示的是c的第一个字符
            if(ccode < codesUsed){//如果当前的代码已经存在了的话,那么就直接输出
                output(ccode);
                //然后把p+c插入到s中去
                if(codesUsed < MAX_CODES){//如果s中还有位置的话
                    //first表示的是前缀,s[size]表示的是fc(c)表示输出的第一个字符
                    ht[codesUsed].first = pcode;
                    ht[codesUsed++].second = s[size];
                }
            }else{
            //后输出的话,我的s[size]表示的是pc的第一个字符
            //因为每一次都是要pcode=ccode的,所以就是说我先输出的话,那么就是s[size]表示的是ccode的第一个字符
            //然后pcode表示其前缀。如果后输出的话,因为s[size]还是前面那个输出的,也就是上一次的ccode的第一个字符,
            //而因为pcode = ccode了,所以也就变成了pccode的第一个字符了此时就是text(pccode)fc(pccode)了
                //如果不存在的话,那么我就存入text(q)fc(q)
                //s[size]表示的是输出的第一个字符
                ht[codesUsed].first = pcode;
                ht[codesUsed++].second = s[size];
                output(ccode);
            }
            pcode = ccode;
        }
    }
    out.close();
    in.close();
}

/*Function: getCode
 *描述: 从压缩的文件中读取代码
 *详细描述:
 *  首先按照压缩的时候的步骤来,因为压缩的时候是先输出8位,然后剩下的4位留着后面输出。所以这个时候我们需要将这8位和4位合并起来了
 *首先,还是要有一个标志位来表示是不是要合并一下8+4位。
 *所以:
 *          //如果读取到文件末尾了,就直接返回false   
 *          if((c = in.get()) == EOF){
 *              return false;
 *          }
 *          //如果有上边剩余的四位的话,那么我直接把上面剩余的四位作为高八位,然后剩下的作为低八位
 *          if(bitsLeftOver)
 *              code = (leftOver << BYTESIZE) | c;
 *          else{
 *              //如果有剩余的四位没有组合的话,我们就需要把两个代码联合起来也就是p的高八位加上c的高四位组成12位输出
 *              d = in.get();
 *              //code是由 c的高八位和d的低四位组合而成的
 *              code = (c << EXCESS) | (d >> EXCESS);
 *          }
 *到最后我们需要翻转一下那个标志位,因为肯定是4位第一次输出,然后第二次不输出,一直翻转的
 *
 */

bool getCode(int &code){
    int c,d;
    //注意我每次获得的字符真正有用的都是8位,因为我压缩代码的时候是每8位输出一个的(也就是一个字节输出)
    if((c = in.get()) == EOF)
        return false;
    if(bitsLeftOver){//如果有剩余的四位
        code = (leftOver<<BYTESIZE) | c;
    }else{
        //如果没有要输出的四位的时候,我们需要给设置一下
        //首先要获取下这个代码是多少
        d = in.get();
        code = (c<<EXCESS) | (d >> EXCESS);
        leftOver = d & MASK;
    }
    bitsLeftOver = !bitsLeftOver;//翻转一下
    return true;
}

/*Function: output
 *描述:将代码对应的value值写入到文件中
 *具体算法:
 *请注意一个原则,我们将关键字存在于字典中的时候,key代表的是代码,value表示一个字符,也就是说用key+value表示要输出的代码
 *也就是key = 304时,value = 'a'时,我们要输出的代码是304a。那么还有一个关键点就是数组的下标。数组的下标其实就是我们读取出来的
 *代码。比方说现在有一个字符串aaabbbbbbaabaaba
 *              那么根据压缩的策略我的输出是0214537
 *那么根据解码策略我们首先把0a,1b放到字典中去
 *              --------------------------
 *              |0|1|2|3|4|5|6|7|
 *              -------------------------
 *              |0|1|0|2|1|4|5|3|
 *              -------------------------
 *              |a|b|a|b|b|b|a|a|
 *              -------------------------
 *输出:a|aa|b|bb|bbb|aab|aaba|
 *注意这个时候我的字典的位置已经是在usedCode = 2了,因为已经有两个位置被用了
 *然后我们开始读取那个压缩的文件,首先读取0因为此时p=NULL,c=0,又因为c在字典中去,所以把0对应的字符输出来,又因为p=NULL表示第一个点
 *所以不用把p+c(的第一个字符加入到字典中去)。然后c继续读取到2,现在p=0,usedCode=2;因为2不在这个字典中(也就是2>=usedCode),然后我就需要把
 *这个2这个编码放到我的字典中去。怎么存放呢,直接ht[usedCode].first = pcode;ht[usedCode++].second = s[size];
 *其中pair的first表示的是这个要输出的字符串的前缀码,然后second表示的是这个字符串要输出的最后一个字符。然后这个usedCode表示的是你这个
 *读取的编码是多少(其实也就是你在数组中的第几个位置)。如果得到这个代码中存在于这个字典中,也就是code= ALPHA){
 *              s[++size] = ht[code].second;//得到这个code对应的value,因为这个代码对应的value肯定是一个前缀码+字符的
 *              code = ht[code].first;
 *          }
 *
 */
void output(int code){
    size = -1;
    while(code >= ALPHA){
    //如果,你得到的结果不是单个单词的话,也就是存在于255以外的话
        s[++size] = ht[code].second;
        code = ht[code].first;
    }
    s[++size] = code;
    for(int i = size;i >= 0;--i)
        out.put(s[i]);
}

int main(int argc,char *argv[]){
    setFiles(argc,argv);
    decompress();
}

你可能感兴趣的:(C++)