C++中的位移操作以實現檔案的壓縮(實現哈夫曼對檔案壓縮與解壓時做的一個小測試)
因為以前基本上沒用過位移操作,所以這裡做了一個小測試,加深了一下對位移的理解
相關概念:
因為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就是我們預設補的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; }
上面是我的一個小測試,主要是因為利用哈夫曼編碼實現檔案的壓縮與解壓時遇到了這個問題。在後面我就會把哈夫曼編碼的程式碼貼出來啦~~~