C++中的位移操作以实现文件的压缩(实现哈夫曼对文件压缩与解压时做的一个小测试)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_33742119/article/details/83715744

因为以前基本上没用过位移操作,所以这里做了一个小测试,加深了一下对位移的理解

相关概念:

       因为C++中对文件的操作常用的就是按字节来进行读取。下面对文件的读写进行举例(这是我常用的方式,大家也可以用其它方法读取):

  首先包含相关头文件:

     #include  <sstream>
     #include  <string>
     #include  <fstream>

  对文件进行读取:

void ReadFile(string filename, int &n, char data[], int weight[]){ //从文件中读取信息
    ifstream  ifs;   //定义一个输入流
    ifs.open(filename);//打开文件,filename是文件名
    if (!ifs)
    {
        cerr << "错误:无法打开文件" << endl;
        exit(OVERFLOW);
    }
    char  str[100];
    int  i = 0;
    while (!ifs.eof())
    {
        if (i < 2)//因为前两行格式与后面的格式不一样,所以加了一个判断
        {
            ifs.getline(str, 100);    //获取前两行文字信息,获取的字符串长度为100并存入str中
            i++;
        }
        else
        {
            DepaList  d = (DepaList)malloc(sizeof(Depa));//DepaList 是一个存储宿舍信息的结构体指针,你可以理解成链表
            if (!d)//空间分配失败
                exit(OVERFLOW);
            d->next = NULL;
            string  buffer;
            getline(ifs, buffer);  //读取数据存入buffer中,buffer里面存储的是这一行所有的信息,但是没有存储行尾的回车
            istringstream  iss(buffer);//将字符串buffer转换为字符串输入流
            int  num;  //宿舍号
            int  numOfStudent;     //可容纳学生人数
            int  nowNumOfStudent;     //已容纳学生人数
            char  sex[10];     //是否是男生宿舍
            iss >> num >> numOfStudent >> nowNumOfStudent >> sex;//利用iss能直接获取这里面的数据,类似于我们的cin

            //下面是对这个结构体的初始化
            d->num = num;
            d->numOfStudent = numOfStudent;
            d->nowNumOfStudent = nowNumOfStudent;

            //因为sex是数组,所以用strcpy来进行赋值,不能 用等号
            strcpy_s(d->sex, sex);
            dhead->next = d;
            dhead = dhead->next;
        }
    }

    ifs.close();
}

       对文件进行写入:

ofstream  ofs;    //存储宿舍信息
ofs.open("Department.txt");
if (!ofs)
{
      cerr << "错误:无法打开文件" << endl;
      exit(OVERFLOW);
}
ofs << "宿舍信息:" << endl;
ofs1 << "宿舍号\t可容纳人数\t已容纳人数\t男生/女生宿舍(请输入男/女)";
if (dl->next)
{
       DepaList  dlhead = dl->next;
       while (dlhead)
       {
              ofs << endl << dlhead->num << "\t" << dlhead->numOfStudent << "\t\t" << dlhead->nowNumOfStudent << "\t\t" << dlhead->sex; //ofs的使用类似于cout
               dlhead = dlhead->next;
        }
}
ofs.close();//关闭文件

     上面讲的是一种对文件进行读写的操作,但是上面都是对字节进行操作的,而不是bit,而我们在读写文件时除了用上述的字符串流进行操作以外,还可以使用get与put函数进行读写,但是它们操作的基本单位都是字节(Byte)。

     一个字符类型占1Byte=8bit

     一个int类型占4Byte,也就是4个字节(依据系统不一样可能会占2Byte)

     一个long类型占4Byte,也就是4个字节

     而我们要进行位移实际上是对bit进行操作,对于char类型来说,它占的是8bit,但是我们的最高位是符号位,所以说我们在进行位移操作时最好不要使用最高位,也就是说我们拼接起来7个bit以后,就可以用put(value)来写入文件了。

位移举例:

      char value=0; //这个地方0没有加单引号,所以表示的是整型,而不是字符

                             //也就是说我们的value存的是ASCII码为0000 0000的一个字符,因为一个字符占8bit

      value <<= 1;   //将value左移一位,默认右边是补0,也就是会变成0000 000   最后这个0就是我们默认补的0

                            //这句话等价于 value = value << 1; 当然类似的也有右移,右移时最高位是补符号位

       value = (value << 1) | 0x80;  //0x80是一个十六进制数,转换为二进制就是1000  0000(如果不清楚转换过程可以度一度)

                                                    //先将value左移一位,也就是0000 0000,再与1000  0000按位或,就变成了1000  0000

                                                    //所以就将value的最高位置为了1,如果想要操作其它位的话就对应修改那个十六进制数就好了

      需要注意的是,我们的字符是8bit,实际上这个8位二进制数转换成十进制后,是这个字符对应的ASCII码

使用背景:

      比如说有一个文件,里面只有三个字符abc,那么这三个字符占的内存大小就是3*8bit=24bit,然后我们要对这个字符进行ASCII编码,假如说a的编码是0,b的编码是10,c的编码是11,如果我们直接将它的哈夫曼编码写入文件的话,是直接以字节(Byte)为单位写入的,所以文件中就是01011,它占的内存大小为5*1Byte=5*8bit=40bit,因此是没有实现文件压缩的。

      正确的做法应该是将它们的哈夫曼编码拼接成7bit,然后再以字节的形式写入文件。但是很明显,我们的编码拼接起来只有5个bit,所以我们要将它们左移两位变成 010 1100(右边补0),这个时候再将这7个bit位以字节的形式写入文件中,当然写进去的就是ASCII码为010 1100的字符,这个时候我们就实现了文件的压缩了,整个文件从24bit变成了8bit。

     但是这样有一个问题,就是我们将哈夫曼编码拼接起来写入文件时,最后一个写入文件的编码可能不足7bit,所以我们就要用到左移的操作来补0 ,这个时候我们解压时就不知道读取到什么时候结束。比如上面那个例子,我们得到的7bit是 010 1100,那么对应的字符就是abcaa,这个时候多读了两个a。解决方法就是用一个变量记录我们文件中字符的个数,当我们解压了这么多字符以后就停止解压,这样就OK啦。

位移操作实现:

#include <iostream>
#include <string>
#include <sstream>
#include<fstream>
using namespace std;

int main(){
    /*

    //这是为了加深对位移的理解做的一个小测试
    int c = 0x11;//c对应的二进制数为0001 0001
    for (int i = 0; i < 8; i++){//循环的取出c中的每一位

        //c<<i,表示的是将c左移i位,初始时左移0位,所以不变,然后依次左移1位、2位。。。。

        //c << i & 0x80,是将c与0x80按位与,所以只有c的最高位保留下来了,其他位&0都是0

        //所以当c的最高位为1时这个表达式非0,也就是真值为真,否则就是假了(c中只有0表示假,其他非零值都是真)
        char tmp = (c << i & 0x80)?'1':'0';//这是c++中的三目运算符

                                                            //当?前面的表达式为真时,则将tmp赋值为字符1,否则赋值为字符0
        cout << tmp;
    }
    */

    char *c = (char*)malloc(sizeof(char)* 15);//给c分配空间
    strcpy(c, "11010011001");//将c赋值为11010011001,c是对哈夫曼编码的模拟
    cout << c << "\t"<<strlen(c)<<endl;
    ofstream  ofs;                                       //写入压缩文件
    ofs.open("11.txt", ios::binary);              //以二进制流形式压缩文件
    if (!ofs)
    {
        cerr << "错误:无法打开文件" << endl;
        exit(OVERFLOW);
    }
    int pos = 0, i = 0;//pos记录已经拼接的bit位
    char value = 0;//这里value的ASCII码为000 0000
    //向压缩文件里写入Huffman编码
    for (i = 0; i < strlen(c); i++)//遍历c中的每一个字符
    {
        value <<= 1;//将value左移1位,value为000 0000,右边默认补0
        if (c[i] == '1')  //需要写入的二进制为1
        {
            value |= 1;//将value与1按位或,也就是000 0000 | 000 0001 =000 0001
        }
        if (++pos == 7)   //满7位写入文件,最高位是符号位
        {
            ofs.put(value);//将字符value写入文件
            value = 0;//将value的ASCII重置为000 0000
            pos = 0;//bit位再从第一位开始
        }
    }

    if (pos)    //最后的编码不满足7bit
    {
        value = value << (7 - pos);//将它左移,直到有7bit为止
        ofs.put(value);
    }
    ofs.close();
    ifstream  ifs;                                //读取压缩文件
    ifs.open("11.txt", ios::binary);      //以二进制流形式解压缩
    if (!ifs)
    {
        cerr << "错误:无法打开文件" << endl;
        exit(OVERFLOW);
    }
    int count = 0;//记录已经读取的位数
    bool flag = false;//标志是否已经读取完了最后一个字符
    string str="";
    char ch;
    while (!ifs.eof()&& !flag)
    {
        ifs.get(ch);//从文件中读取一个字符并赋值给ch
        cout << ch << endl;
        ch <<= 1;//将最高位左移,取有效的7个bit位
        for (int pos = 0; pos < 7; pos++){
            string tmp = (ch << pos & 0x80) ? "1" : "0";
            str.append(tmp);//将取出的bit位拼接成字符串
            ++count;//记录读取出的有效位数
            cout << tmp<<"\t"<<count<<endl;
            if (count >= strlen(c)){//所有有效位都取出来了,就不取补的那些0了,直接跳出循环
                flag = true;
                break;
            }    
        }    
    }
    cout << str;
    ifs.close();
    free(c);
    return 0;
}

上面是我的一个小测试,主要是因为利用哈夫曼编码实现文件的压缩与解压时遇到了这个问题。在后面我就会把哈夫曼编码的代码贴出来啦~~~

 

 

 

 

 

 

展开阅读全文