1. 程式人生 > >WX Never Give Up!

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");*/

}
“`