WX Never Give Up!
有關於檔案壓縮的思想和問題。
huffman tree不單單針對檔案壓縮,也可以用在其他地方
編碼時候更關注葉子結點。
直接利用二叉樹前中後序遍歷二叉樹找葉子結點,但前提是必須是三叉鏈。
生成Huffman編碼:第一種是_infos[root->_w._ch]._code = code;//字元的ASCII碼//將編碼存入到葉子結點的權值的字元中
第二種是string& code = _infos[root->_w._ch]._code;//沒有建立物件,沒有拷貝,可讀性變高
檔案壓縮
問題:
1.漢字不能正確壓縮
因為漢字通常是由負數的ASCII碼組成,_infos[]不能訪問到。
只能強轉(unsigned char)
問題1:存在漢字的檔案,壓縮時程式會崩,所以需要讓程式支援漢字字元壓縮。
打斷點找存在漢字程式就會崩的問題所在。
只需要將_infos[root->_w._ch]._code改為_infos[(unsigned char)root->_w._ch]._code就可以了,是為了將負數值的ASCII碼所對應的漢子字元讀出。
原始檔:
解壓縮後的檔案:
2.如果需要壓縮的資料過多,就不能正確壓縮
解壓縮與檔案的隨機性有關。(!待解決!)
需要判斷是壓縮的問題還是解壓縮的問題。。。
(1)沒有解壓縮完全
通過記錄壓縮的字元個數,與解壓縮的字元個數比較,檢視是否是解壓縮的問題
一種可能是沒有讀的字元個數不足,說明解壓縮時候讀壓縮檔案有誤。
另一種可能是壓縮的寫的字元個數不夠,說明壓縮時候寫檔案有誤。
。。以上通過記錄個數除錯。。。
問題2:現階段少部分的字元格式的檔案是可以被壓縮與解壓縮的。
原始檔:
壓縮後的檔案:
解壓縮後的檔案:
但是程式仍舊存在問題,如果檔案記憶體的字元過多(具體的數值大小不清楚),就會出現問題。
壓縮前:
解壓縮後:
結果並不正確!
所以要解決問題,就要知道程式出錯的位置,要測試看到底是壓縮的過程出現問題還是解壓縮的過程出現問題。
所以,為什麼解壓縮的時候解壓縮的長度具有隨機性?
提前遇到EOF。
檔案格式。
檔案沒有讀完,就提前遇到了EOF,為什麼?
文字檔案是可以讀的,二進位制檔案——音訊、視訊、圖片
文字檔案通過EOF判斷結束,一般來說,-1的ASCII碼是不用的
文字檔案裡面不會出現-1,但二進位制檔案可能出現-1,所以不可以用EOF單純判斷結束。
解決方式:
feof()要比EOF多讀一次。
feof()檔案結束符,只有當檔案結束或者錯誤的時候其返回的值是非零值,否則為零,比EOF好的地方在於省去了EOF判斷是-1的時候會影響結果的情況。
所以將while迴圈的判斷條件改為 !foef(fout) ,讓ASCII碼為-1的字元不會影響結果。
1.文字方式讀寫
2.二進位制讀寫
檔案系統——管理檔案(FEOF——讀到最後一次不會停,會多讀一次)
會有介面來管理檔案,比如說檔案指標,下標移動讀檔案。
一般情況,先單獨壓縮,在單獨解壓縮。
壓縮是沒有問題,解壓縮有問題,解壓縮依賴Huffman tree,需要字元出現的次數。
為什麼壓縮與解壓縮一起執行的時候解壓縮沒有出問題?
因為壓縮與解壓縮用的是同一個物件,即同一個Huffman tree,所以解壓縮是有字元出現的次數是存在的。
但根源問題是在壓縮的時候就要記錄。
文字和二進位制形式寫,
所以兩者佔的空間是一樣的。
文字形式
在壓縮之後,寫字元出現的次數到壓縮檔案,——解壓縮時重建Huffman tree
char buffer[128];
for(size_t i=0;i<256;++i)
{
if(_infos[i]._count > 0)
{
fputc(_infos[i]._ch,fin);
fputc(’ ‘,fin);
itoa(_infos[i]._count,buffer,10);
fputs(buffer,fin);
fputc(‘\n’,fin);
}
}
字元分為可讀和不可讀的。。。。。
將次數讀出來使用itoa變成字串儲存。
二進位制形式
//為什麼fwrite既有size還有count?
在記憶體的角度和磁碟的角度:
count 物件的number(個數),size是每一次準備寫的每個物件的位元組數。
二進位制形式任意物件都可以寫進去。
char buffer[128];
for(size_t i=0;i<256;++i)
{
if(_infos[i]._count > 0)
{
fwrite(&_infos[i],sizeof(CharInfo),1,fin);
}
}
二進位制形式比文字形式所佔空間更大。(不一定)
為什麼二進位制形式寫進去所佔的位元組數更多?
文字形式必須以分隔符區分,而二進位制形式位元組數是固定的,當文字形式內容更多時,就會存在文字形式會佔跟多的空間,二進位制形式不需要分隔符區分字元,8個位元組就表示一個字元,多出來的位用0補位(位元組對齊)。所以文字形式比一定就比二進位制形式所佔空間小。
在類中定義類,內部類,但受到訪問限定符的限,不暴露在外部。
為了傳輸節省頻寬,就將Huffman tree中的內容存成配置檔案,與壓縮檔案一起存。
先讀入配置資訊——字元出現的次數
循壞判斷,一直讀,如果當碰到info._count==0,就break;
否則,就將獨處的檔案放入——_infos[]中。
為什麼要以count==0來分隔配置檔案和壓縮檔案?
首先,壓縮檔案存的是Huffman tree中存的內容(即字元及其出現的次數)的配置檔案,中間用分隔區分壓縮檔案和配置檔案。
當讀到count==0——分隔符時候,就表示上面的是配置檔案已經讀完,剩餘的就是壓縮檔案。
input.txt就是原始檔
input.txt.huffman就是壓縮後的壓縮檔案
input.txt.unhuffman就是解壓縮後的檔案
Heap.h檔案(堆)
#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
template<class T,class Compare>
class Heap //仿函式管理大小堆更方便
{
public:
Heap()
{}
Heap(T* a, size_t n)
{
_a.reserve(n);
for (size_t i = 0; i < n; ++i)
{
_a.push_back(a[i]);
}
//建堆
for (int i = (_a.size() - 2) / 2; i >= 0; --i)//需要等於0所以用int
{
AdjustDown(i);
}
}
size_t Size()
{
return _a.size();
}
void AdjustDown(int root)//向下調整
{
Compare com;
int parent = root;
int child = parent * 2 + 1;
while (child < _a.size())
{
if (child + 1 < _a.size()
&& com(_a[child + 1],_a[child]))//比較出大的孩子
{
++child;
}
if (com(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
parent = child;
child = parent * 2 + 1;
}
else //表示父親大於孩子
{
break;
}
}
}
void Push(const T& x)
{
_a.push_back(x);
AdjustUp(_a.size() - 1);
}
void AdjustUp(int child)//向上調整
{
Compare com;
int parent = (child - 1) / 2;
while (child > 0)//這裡的parent不可能為負數
{
//if (_a[child]>_a[parent])
if (com(_a[child],_a[parent]))
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Pop()//pop最大的資料
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);
_a.pop_back();
AdjustDown(0);
}
T& Top()
{
assert(!_a.empty());
return _a[0];
}
private:
vector<T>_a;
};
template<class T>
struct Less
{
bool operator()(const T& l, const T& r)
{
return l < r;
}
};
template<class T>
struct Greater
{
bool operator()(const T& l, const T& r)
{
return l > r;
}
};
void AdjustDwon(int* a, size_t n, int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n
&& a[child + 1] > a[child])
{
++child;
}
if (a[parent] < a[child])
{
swap(a[parent], a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
HuffmanTree.h檔案(哈夫曼樹)
pragma once
//包堆的標頭檔案
include”Heap.h”
template//W存權值
struct HuffmanTreeNode
{
HuffmanTreeNode* _left;
HuffmanTreeNode* _right;
HuffmanTreeNode* _parent;
W _w;
HuffmanTreeNode(const W& w)
:_w(w)
, _left(NULL)
, _right(NULL)
, _parent(NULL)
{}
};
template
class HuffmanTree
{
typedef HuffmanTreeNode Node;
public:
HuffmanTree()
:_root(NULL)
{}
HuffmanTree(W *a, size_t n, const W& invalid)//只比較出現的字元,部分比較,所以使用非法值invalid
{
struct NodeCompare
{
bool operator()(Node* l, Node* r)//按權值比較
{
return l->_w < r->_w;
}
};
Heap<Node*,NodeCompare> minHeap;//根據W中的權值比較而不是指標比較
for (size_t i = 0; i < n; ++i)
{
if (a[i] != invalid)//限定比較的字元
{
minHeap.Push(new Node(a[i]));
}
}
while (minHeap.Size()>1)//小堆
{
Node* left = minHeap.Top();
minHeap.Pop();
Node* right = minHeap.Top();
minHeap.Pop();
Node* parent = new Node(left->_w + right->_w);
parent->_left = left;
parent->_right = right;
left->_parent = parent;
right->_parent = parent;
minHeap.Push(parent);
}
_root = minHeap.Top();
}
Node* GetRoot()
{
return _root;
}
protected:
Node* _root;
};
void TestHuffmanTree()
{
int a[] = { 1, 2, 3, 4 };
HuffmanTree t(a, sizeof(a) / sizeof(a[0]), 0);
}
FileCompress.h檔案(檔案壓縮與解壓縮)
pragma once
include
include
include
//#define CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES
define DEBUG
include”HuffmanTree.h”
struct CharInfo
{
char _ch;//存的字元
long long _count;//某字元出現的次數
string _code;//編碼
bool operator !=(const CharInfo& info)
{
return info._count != _count;
}
CharInfo operator+(const CharInfo& info)
{
CharInfo ret;
ret._count = _count + info._count;
return ret;
}
bool operator <(const CharInfo& info)
{
return _count < info._count;
}
};
class FileCompress
{
typedef HuffmanTreeNode Node;
//內部類 受訪問限定符限制
struct ConfigInfo//解壓縮配置
{
char _ch;
size_t _count;
};
public:
FileCompress()
{
for (size_t i = 0; i < 256; ++i)//字元個數是確定的,巨集可用可不用
{
_infos[i]._ch = i;
_infos[i]._count = 0;
}
}
//input.txt->input.txt.huffman
void Compress(const char*file)
{
assert(file);
//1.統計字元出現的次數
FILE*fout = fopen(file, "rb");
//if (fout == NULL)//判斷檔案是否為空 或者檔案是否正確
// perror("Error opening file");//檔案為空輸出錯誤資訊
char ch = fgetc(fout);
while (!feof(fout))
{
_infos[(unsigned char)ch]._count++;
ch = fgetc(fout);
}
//2.構建huffman tree
CharInfo invalid;
invalid._count = 0;
HuffmanTree<CharInfo> tree(_infos, 256, invalid);
//3.生成Huffman code(Huffman編碼)
string code;
GetHuffmanCode(tree.GetRoot(), code);
//4.壓縮
string compressFile = file;
long long count = 0;
compressFile += ".huffman";
FILE*fin = fopen(compressFile.c_str(), "wb");//開啟檔案
assert(fin);
//寫字元出現的次數到壓縮檔案---解壓縮時候需要重建Huffman tree
char arr[128];//arr儲存整型轉為字串後的內容
ConfigInfo info;
for (size_t i = 0; i < 256; ++i)
{
if (_infos[i]._count >0)
{
//二進位制形式寫字串
info._ch = _infos[i]._ch;
info._count = _infos[i]._count;
fwrite(&info, sizeof(ConfigInfo), 1, fin);//
////文字的方式寫字串 一個字元+次數
//fputc(_infos[i]._ch, fin);
//fputc(' ', fin);
////整型轉換成字串
//_itoa(_infos[i]._count, arr, 10);
//fputs(arr, fin);
//fputc('\n', fin);
}
}
info._count = 0;//在最後加上一個0,用來分隔
fwrite(&info, sizeof(ConfigInfo), 1, fin);
fseek(fout, 0, SEEK_SET);//檔案指標
ch = fgetc(fout);//1.先關掉檔案再開啟檔案 2.或者把檔案指標設定到檔案開始
char value = 0;//表示值
int pos = 0;//表示位
//while (ch != EOF)//檔案結束符,當檔案指標到檔案結束時,其值為非零值,否則為零。
while (!feof(fout))
{
//...如何壓縮?
//滿8位就寫入一次...
string& code = _infos[(unsigned char)ch]._code;//重要的是取字元的編碼
for (size_t i = 0; i < code.size(); ++i)//迴圈處理滿8位就寫入一次
{
if (code[i] == '0')
{
value &= ~(1 << pos);
}
else if (code[i] == '1')
{
value |= (1 << pos);
}
else
{
assert(false);
}
++pos;
if (pos == 8)
{
count++;
fputc(value, fin);
value = 0;
pos = 0;
}
}
ch = fgetc(fout);
}
cout << "壓縮字元數:" << count << endl;
//補齊
if (pos > 0)//表示還有位,補齊多餘的位
{
fputc(value, fin);
}
fclose(fout);
fclose(fin);
}
//input.txt.huffman->input.txt解壓縮
void GetHuffmanCode(Node* root)
{
if (root == NULL)
return;
if (root->_left == NULL
&& root->_right == NULL)
{
//沒有建立物件,沒有拷貝,可讀性變高
string& code = _infos[(unsigned char)root->_w._ch]._code;
Node* cur = root;
Node* parent = cur->_parent;
while (parent)//倒著走把編碼存到code中
{
if (cur == parent->_left)
{
code.push_back('0'); //[]用來修改,但不能做插入
}
else
{
code.push_back('1');
}
cur = parent;
parent = cur->_parent;
}
reverse(code.begin(), code.end());//迭代器code.begin()返回char*
//字元的ASCII碼//將編碼存入到葉子結點的權值的字元中
//_infos[root->_w._ch]._code = code;
//return;
}
GetHuffmanCode(root->_left);
GetHuffmanCode(root->_right);
}
void GetHuffmanCode(Node* root, string code)//向左邊走code+0,右邊走code+1
{
if (root == NULL)
return;
if (root->_left == NULL
&& root->_right == NULL)
{
//葉子
//強轉成有符號數,因為漢字的編碼是負數編碼,且一個漢字佔兩個位元組
_infos[(unsigned char)root->_w._ch]._code = code;
return;
}
GetHuffmanCode(root->_left, code + '0');//不可以用pushback
GetHuffmanCode(root->_right,code + '1');
}
//input.txt.huffman -> input.txt
void UnCompress(const char*file)
{
assert(file);
string uncompressFile = file;
size_t pos = uncompressFile.rfind('.');//找'.'
assert(pos != string::npos);
uncompressFile.erase(pos, uncompressFile.size()-pos);
ifdef DEBUG //使用巨集可以更好的將原始檔和解壓縮後的檔案區分開,也便於除錯
uncompressFile += ".unhuffman";//解壓縮後的生成檔案
endif
FILE*fout = fopen(file, "rb");
assert(fout);
FILE*fin = fopen(uncompressFile.c_str(), "wb");
assert(fin);
//先讀入配置資訊---字元出現的次數
ConfigInfo info;
while (1)
{
fread(&info, sizeof(ConfigInfo), 1, fout);
if (info._count == 0)
{
break;
}
else
{
_infos[(unsigned char)info._ch]._ch = info._ch;
_infos[(unsigned char)info._ch]._count= info._count;
}
}
//解壓縮
//1.重建huffman tree
CharInfo invalid;
invalid._count = 0;
HuffmanTree<CharInfo> tree(_infos, 256, invalid);
Node*root = tree.GetRoot();
Node*cur = root;
long long fileSize = root->_w._count;
//2.解壓縮
char value = fgetc(fout);
//fseek(fout, 0, SEEK_SET);
long long count = 0;
//while (value != EOF)
while (!feof(fout))
{
count++;
for (int pos = 0; pos < 8; ++pos)
{
if (value & (1 << pos))//pos為1
cur = cur->_right;
else
cur = cur->_left;
if (cur->_left == NULL
&& cur->_right == NULL)
{
fputc(cur->_w._ch,fin);
cur = root;//還原一個字元
if (--fileSize == 0)
{
break;//只有最後一個字元出來(結束)
}
}
}
value = fgetc(fout);
}
cout << "解壓縮字元數:" << count << endl;
}
private:
CharInfo _infos[256];//在陣列中找編碼
};
void TestFileCompress()
{
FileCompress fc;
//fc.Compress(“input.txt”);
fc.UnCompress(“input.txt.huffman”);
/*FileCompress fc1;
fc1.Compress("input.txt");*/
/*FileCompress fc2;
fc2.UnCompress("input.txt.huffman");*/
}
“`