文件壓縮(Huaffman樹的概念及其實現)

什么是壓縮

想辦法讓源文件變得更小并能還原。

為什么要進行文件壓縮

  1. 文件太大,節省空間
  2. 提高數據再網絡上傳輸的效率
  3. 對數據有保護作用—加密

文件壓縮的分類

  1. 無損壓縮
    • 源文件被壓縮后,通過解壓縮能夠還原成和源文件完全相同的格式
  2. 有損壓縮
    • 解壓縮之后不能將其還原成與源文件完全相同的格式–解壓縮之后的文件再識別其內容時基本沒有影響

GZIP壓縮

LZ77變形:

原理將重復出現得語句用盡可能短得標記來替換
字符串的壓縮
LZ77可以消除文件中重復出現的語句,但還存在字節方面的重復

基于Huffman編碼得壓縮

基于字節的壓縮
1個字節—>8個bit位,如果對于每個字節如果能夠找到一個更短的編碼,來重新改寫原3數據,可以起到壓縮的目的

Huffman樹的概念

從二叉樹的根結點到二叉樹中所有葉結點的路徑長度與相應權值的乘積之和為該二叉樹的帶權路徑長度WPL。
把帶權路徑最小的二叉樹稱為Huffman樹

Huffman樹的概念

用戶提供一組權值信息

  1. 以每個權值為結點創建N棵二叉樹的森林
  2. 如果二叉樹森林中有超過兩個樹,進行以下操作
    • 從二叉樹森林中取出根結點權值最小的兩棵二叉樹
    • 以該兩棵二叉樹作為某個結點的左右子樹創建一顆新的二叉樹,新二叉樹中的權值為其左右子樹權值之和
    • 將新創建的二叉樹插入到森林中

創建好之后的二叉樹森林中的每棵樹可以采用堆(priority_queue)保存

Huffman樹的創建

Huffman樹的結點定義

struct HuffManTreeNode
{HuffManTreeNode(const W& weight = W()):_pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _weight(weight){}HuffManTreeNode<W>* _pLeft;HuffManTreeNode<W>* _pRight;HuffManTreeNode<W>* _pParent;//加上雙親指針域W _weight; //結點的權值
};

Huffman樹的類

template<class W>
class HuffManTree
{typedef HuffManTreeNode<W> Node;
public:HuffManTree():_pRoot(nullptr){}HuffManTree(const vector<W>& vWeight, const W& invaild){//進行建樹操作CreatHuffManTree(vWeight,invaild);}~HuffManTree(){_DestroyTree(_pRoot);}Node* GetRoot(){return _pRoot;}void CreatHuffManTree(const vector<W>& vWeight,const W& invaild){//1.構建森林//底層為優先級隊列priority_queue<Node*,vector<Node*>,Less<W>> q;for (auto e : vWeight){//默認插入的數據沒有重復if (e == invaild)//如果是無效的權值,就不放了continue;q.push(new Node(e));//創建新的結點,樹就有了}//2.看森林里面有沒有超過兩個樹while (q.size() > 1){//取根結點權值最小的兩顆樹Node* pLeft = q.top();q.pop();Node* pRight = q.top();q.pop();// 以該兩棵二叉樹作為某個結點的左右子樹創建一顆新的二叉樹,//新二叉樹中的權值為其左右子樹權值之和Node* pParent = new Node(pLeft->_weight + pRight->_weight);pParent->_pLeft = pLeft;pParent->_pRight = pRight;//將新數插入到Huffman樹中pLeft->_pParent = pParent; //更新雙親pRight->_pParent = pParent;q.push(pParent);}//3.森林里只有一棵樹了,就是哈夫曼樹_pRoot = q.top();}private://銷毀二叉樹,只能用后序遍歷的方式進行銷毀void _DestroyTree(Node*& pRoot){if (pRoot){_DestroyTree(pRoot->_pLeft);_DestroyTree(pRoot->_pRight);delete pRoot;//要改指針本身,要么傳二級指針,要么一級指針的引用pRoot = nullptr;}}
private:Node * _pRoot;
};

基于Huffman編碼的壓縮和解壓縮

壓縮

  1. 統計源文件中每個字節出現的次數
  2. 用統計的結果創建Huffman樹
    • 什么是Huffman樹:帶權路徑長度最短的一顆二叉樹----權值越大,越靠近根結點
    • Huffman樹創建規則:
      • 用所有的權值創建只有根結點的二叉樹森林
      • 從二叉樹森林中取出根結點權值最小的兩棵二叉樹
      • 以該兩棵二叉樹作為某個結點的左右子樹創建一顆新的二叉樹,新二叉樹中的權值為其左右子樹權值之和
      • 將新創建的二叉樹插入到森林中
  3. 取權值最小,用優先級隊列也就是堆
  4. 從Huffman樹種獲取字節的編碼
void GenerateHuffmanCode(pRoot)
{if(nullptr == pRoot )return ;GenerateHuffmanCode(pRoot->left);GenerateHuffmanCode(pRoot->right);//是葉子結點獲取編碼if(nullptr == pRoot->left&& nullptr == pRoot->right){Node* pParent =pRoot->parent;Node* pCur = pRoot;while(pParent){if(pCur == pParent->left)str+='0';elsestr+='1';}}
}

在這里插入圖片描述

壓縮文件實現

我們將文件中出現的字符信息保存在一個大小為256的數組中

//構造函數
FileCompressHuff::FileCompressHuff()
{_fileInfo.resize(256);for (int i = 0; i < 256; ++i){_fileInfo[i]._ch = i;_fileInfo[i]._count = 0;}
}

每一個數組的元素都是一個結構體類型,里面要保存該字符的信息,如:編碼,出現的次數
而且要支持字符之間的一些運算

struct CharInfo
{unsigned char _ch;		//具體的字符size_t _count; //字符出現的次數std::string _strCode;//字符編碼CharInfo(size_t count = 0):_count(count){}CharInfo operator+(const CharInfo& c){//返回對象,無名對象return CharInfo(_count + c._count);}bool operator>(const CharInfo& c){return _count > c._count;}//比較次數bool operator==(const CharInfo& c){return _count == c._count;}
};

最后再按照上面的四步來完成壓縮的任務

void FileCompressHuff::CompressFile(const string& path)
{//1.統計源文件中每個字符出現的次數FILE* fIn = fopen(path.c_str(),"rb");if (nullptr == fIn){assert(false);return;}unsigned char * pReadBuff = new unsigned char[1024];int rdSize = 0;while (true){//讀文件rdSize = fread(pReadBuff, 1, 1024, fIn);	if (0 == rdSize)break;//統計for (int i = 0; i < rdSize; ++i){_fileInfo[pReadBuff[i]]._count++;}}//2.以這些出現的次數為權值創建哈夫曼樹HuffManTree<CharInfo> t(_fileInfo,CharInfo());//3.獲取每個字符的編碼GenerateHuffManCode(t.GetRoot());//4.獲取到的字符編碼重新改寫FILE* fOut = fopen("2.txt", "wb");  //打開一個文件保存壓縮后的結果if (nullptr == fOut){assert(false);return;}//將來要保存文件的后綴,所以壓縮時要讀取壓縮格式文件頭部信息WriteHead(fOut, path);fseek(fIn,0,SEEK_SET); //把文件指針再次放到文件的起始位置char ch = 0;	//存放字符編碼int bitcount = 0; //計算放了多少個比特位while (true){rdSize = fread(pReadBuff, 1, 1024,fIn);if (0 == rdSize)  //文件讀取結束break;//根據字節的編碼對讀取到的內容進行重寫for (size_t i = 0; i < rdSize; ++i){//拿到編碼string strCode = _fileInfo [pReadBuff[i]]._strCode;//A "100"for (size_t j = 0; j < strCode.size(); ++j){ch <<= 1; //每放一個往左一位,把下一個編碼往里放//存放的時候,一個一個比特位來放if ('1' == strCode[j])ch |= 1;bitcount++;if (8 == bitcount) //說明ch滿了,把這個字節寫到文件中去{//fputc 寫單個字節的函數fputc(ch, fOut);//寫完后,都清零bitcount = 0;ch = 0;}}}}//最后一次ch中可能不夠8個比特位if (bitcount < 8){ch <<= 8 - bitcount; //左移剩余的位數//不夠的位肯定要寫到文件中fputc(ch, fOut);}delete[]pReadBuff;fclose(fIn);fclose(fOut);
}

獲取字符編碼

//計算字符編碼
void FileCompressHuff::GenerateHuffManCode(HuffManTreeNode<CharInfo>* pRoot)
{if (nullptr == pRoot)return;GenerateHuffManCode(pRoot->_pLeft);GenerateHuffManCode(pRoot->_pRight);//找到葉子結點if (nullptr == pRoot->_pLeft&&pRoot->_pRight == nullptr){string& strCode = _fileInfo[pRoot->_weight._ch]._strCode;HuffManTreeNode<CharInfo>*  pCur = pRoot;HuffManTreeNode<CharInfo>*  pParent = pCur->_pParent;while (pParent){if (pCur == pParent->_pLeft)	//左為0{strCode += '0';}else							//右為1{strCode += '1';}pCur = pParent;pParent = pCur->_pParent;}//字符編碼從葉子結點開始獲取是個反的//我們需要把它翻轉一下reverse(strCode.begin(), strCode.end());//_fileInfo[pRoot->_weight._ch]._strCode = strCode;}
}

獲取文件后綴

//獲取文件的后綴
//2.txt
//F:\123\2.txt
string FileCompressHuff:: GetFilePostFix(const string& filename)
{//substr截取文件,第二個參數沒有給的話默認截取到末尾return  filename.substr(filename.rfind('.'));
}

壓縮文件的格式

壓縮文件中只保存壓縮之后的數據可以嗎?
答案是不行的,因為在解壓縮時,沒有辦法進行解壓縮。比如:10111011 00101001 11000111 01011,只有壓縮數據是沒辦法進行解壓縮的,因此壓縮文件中除了要保存壓縮數據,還必須保存解壓縮需要用到的信息:

  1. 源文件的后綴
  2. 字符次數對的總行數
  3. 字符以及字符出現次數(為簡單期間,每個字符放置一行
  4. 壓縮數據
//壓縮文件格式
void FileCompressHuff::WriteHead(FILE* fOut, const string& filename)
{assert(fOut);//寫文件的后綴string strHead;strHead += GetFilePostFix(filename);strHead += '\n'; //后綴與后面的內容之間用\n隔開//寫行數size_t lineCount = 0;string strChCount;char szValue[32] = { 0 };	//緩沖區放入字符次數for (int i = 0; i < 256; ++i){CharInfo& charInfo = _fileInfo[i];if (charInfo._count){lineCount++;	//行數strChCount += charInfo._ch; //字符strChCount += ':';	//字符與字符次數之間用冒號隔開_itoa(charInfo._count, szValue, 10);strChCount += szValue;		//字符次數strChCount += '\n';	//末尾加\n}}_itoa(lineCount, szValue, 10);//接受字符的行數strHead += szValue;	//字符的行樹strHead += '\n';	//換行隔開信息strHead += strChCount; //字符的種類個數//寫信息fwrite(strHead.c_str(),1,strHead.size(),fOut);
}

解壓縮

  1. 從壓縮文件中獲取源文件的后綴
  2. 從壓縮文件中獲取字符次數的總行數
  3. 獲取每個字符出現的次數
  4. 重建huffman樹
  5. 依靠Huffman樹解壓縮解壓縮

要解壓縮文件我們先要讀取壓縮文件的格式信息
//讀取壓縮文件的信息

void FileCompressHuff::ReadLine(FILE* fIn, string& strInfo)
{assert(fIn);//讀取一行的字符while (!feof(fIn))  //只要文件指針沒有到文件末尾就讀取{//讀取一個字符char ch = fgetc(fIn);if (ch == '\n')break;//有效的字符就拼接上去strInfo += ch;}
}

按照上面的步驟完成解壓縮

void FileCompressHuff::UNComPressFile(const std::string& path)
{FILE* fIn = fopen(path.c_str(), "rb");if (nullptr == fIn){assert(false);return;}//讀取文件的后綴string strFilePostFix;ReadLine(fIn,strFilePostFix);//讀取字符信息的總行數string strCount;ReadLine(fIn, strCount);int lineCount = atoi(strCount.c_str()); //總的行數//讀取字符的信息for (int i = 0; i < lineCount; ++i){string strchCount;ReadLine(fIn, strchCount); //讀每一行的字符信息//如果讀取到的是\nif (strchCount.empty()){strchCount += '\n'; //將\n寫入讀取ReadLine(fIn, strchCount); //多讀一行,將\n的次數和冒號讀取}//A:100_fileInfo[(unsigned char)strchCount[0]]._count = atoi(strchCount.c_str() + 2);//跳過前兩個字符,因為前兩個是A:}//還原Huffman樹HuffManTree<CharInfo> t;  //創建Huffamn樹的對象t.CreatHuffManTree(_fileInfo, CharInfo(0)); //還原Huffman樹FILE* fOut = fopen("3.txt","wb");assert(fOut);//解壓縮char* pReadBuff = new char[1024];	//創建緩沖區char ch = 0;HuffManTreeNode<CharInfo>* pCur = t.GetRoot();size_t fileSize = pCur->_weight._count; //文件總的大小就是根結點的權值的次數size_t uncount = 0;//表示解壓縮了多少個while (true){size_t rdSize = fread(pReadBuff,1,1024,fIn); //讀數據if (0 == rdSize)break;//一個個字節進行解壓縮for (size_t i = 0; i < rdSize; ++i){//只需要將一個字節中的8個比特位單獨處理ch = pReadBuff[i];for (int pos = 0; pos < 8; ++pos){//增加一次判斷if (nullptr == pCur->_pLeft && nullptr == pCur->_pRight){//uncount++; //每次解壓縮一個,就++一下fputc(pCur->_weight._ch, fOut);if (uncount == fileSize)break;//葉子結點,解壓出一個字符,寫入文件			pCur = t.GetRoot(); //把pCur放到樹根的位置上繼續}if (ch & 0x80)//如果該位上的數字為1pCur = pCur->_pRight;elsepCur = pCur->_pLeft;ch <<= 1; //與完后往左移動一位if (nullptr == pCur->_pLeft && nullptr == pCur->_pRight){uncount++; //每次解壓縮一個,就++一下fputc(pCur->_weight._ch, fOut);if (uncount == fileSize)break;//葉子結點,解壓出一個字符,寫入文件			pCur = t.GetRoot(); //把pCur放到樹根的位置上繼續}}//for循環完后再讀取下一個字節}}delete[]pReadBuff;fclose(fIn);fclose(fOut);
}

出現的問題

1. 加中文

問題1

壓縮時統計源文件中每個字符出現的次數時,出現崩潰,因為數組越界,因為char[-128.127];
修改:將char改成unsigned char;

問題2

獲取編碼時崩潰,將CharInfo結構體中的char改為unsigned char

問題3

解壓縮函數讀取字符信息時崩潰,strchCount要作為數組下標必須為整數,將其強轉為unsigned char

2.多行信息

問題1

解壓縮函數中的解壓縮時崩潰,因為pCur為空了,因為讀取文件時,讀取空格就直接什么都沒有讀到,所以遇到空格時應該多讀一行,在解壓縮讀取文件時處理\n

問題2

文件量增大時,只能解壓縮一部分,將所有的文件打開和寫入方式轉變為以二進制形式打開和寫入,并在解壓縮時再增加一次判斷

完整代碼:

要改進的方面

  1. 保證壓縮的正確性
  2. 壓縮比率
  3. 是不是每次壓縮之后變小?有沒有可能壓縮變大?
  4. 文本文件,視頻,音頻,圖片,都可以壓縮?
  5. 其它改進的方式

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383091.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383091.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383091.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

詳解STL中的空間配置器(SGI版本)

空間配置器 1.什么是空間配置器 為各個容器高效的管理空間(空間的申請與回收)的 2.為什么需要空間配置器 各種容器----->可以存放元素---->底層需要空間 new 申請空間 operator new ---->malloc調用構造函數------完成對象的構造 動態內存管理總結 前面的容器…

【劍指offer】_15數組中重復的數字

題目描述 在一個長度為n的數組里的所有數字都在0到n-1的范圍內。 數組中某些數字是重復的&#xff0c;但不知道有幾個數字是重復的。也不知道每個數字重復幾次。請找出數組中任意一個重復的數字。 例如&#xff0c;如果輸入長度為7的數組{2,3,1,0,2,5,3}&#xff0c;那么對應的…

【劍指offer】_16 構建乘積數組

題目描述 給定一個數組A[0,1,…,n-1],請構建一個數組B[0,1,…,n-1],其中B中的元素B[i]A[0]*A[1]*...*A[i-1]*A[i1]*...*A[n-1]。不能使用除法。 解題思路 設有數組大小為5。 對于第一個for循環 第一步&#xff1a;b[0] 1; 第二步&#xff1a;b[1] b[0] * a[0] a[0] 第三…

【劍指offer】_17正則表達式的匹配

題目描述 請實現一個函數用來匹配包括’.‘和*的正則表達式。模式中的字符’.表示任意一個字符&#xff0c;而*表示它前面的字符可以出現任意次&#xff08;包含0次&#xff09;。 在本題中&#xff0c;匹配是指字符串的所有字符匹配整個模式。例如&#xff0c;字符串"aaa…

大四階段的社會實踐的主要目的是_疫情當前,大三大四的學生“很慘”?大一大二的學生也別松懈...

大四畢業生不容易這次疫情對于高校學生而言&#xff0c;可以說是各有各的難處&#xff0c;“這屆畢業生很慘”更是屢上熱搜。不可否認&#xff0c;大四畢業生確實很不容易&#xff0c;論文答辯、畢業、求職就業等都受到了影響&#xff0c;雖然有困難&#xff0c;但各方都在積極…

【劍指offer】_18 數據流中的中位數

題目描述 如何得到一個數據流中的中位數&#xff1f;如果從數據流中讀出奇數個數值&#xff0c;那么中位數就是所有數值排序之后位于中間的數值。如果從數據流中讀出偶數個數值&#xff0c;那么中位數就是所有數值排序之后中間兩個數的平均值。我們使用Insert()方法讀取數據流…

【劍指offer】_19 滑動窗口中的最大值

題目描述 給定一個數組和滑動窗口的大小&#xff0c;找出所有滑動窗口里數值的最大值。例如&#xff0c;如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3&#xff0c;那么一共存在6個滑動窗口&#xff0c;他們的最大值分別為{4,4,6,6,6,5}&#xff1b; 針對數組{2,3,4,2,6,2,…

android 文字反轉_多文字共享信息系統

歐陽貴林 www.HeZi.net首發表于2016年03月23日“ 處在信息時代的開端&#xff0c;信息技術不應有特殊的文字性&#xff0c;需要創建多文字共享信息系統&#xff0c;給各國文字一個公平的參與信息與科技創新發展的平臺。這是世界的事&#xff0c;更是中國事。”01人類語言語言文…

LeetCode【1--兩數之和】 LeetCode【2--兩數相加】

兩數之和 題目描述 給定一個整數數組 nums 和一個目標值 target&#xff0c;請你在該數組中找出和為目標值的那 兩個 整數&#xff0c;并返回他們的數組下標。 你可以假設每種輸入只會對應一個答案。但是&#xff0c;你不能重復利用這個數組中同樣的元素。 解題思路 直接兩…

input數字開頭不能為0_李商隱為初戀寫詩,每句以數字開頭,最后10字一直被仿從未被超越...

上學時&#xff0c;每次寫作文&#xff0c;老師總愛在耳邊念叨&#xff1a;“你的作文得讓閱卷老師看得懂&#xff0c;不然不可能給你高分的&#xff01;”每次聽到話&#xff0c;筆者總是用李商隱的詩來和他斗嘴。是的&#xff0c;李商隱的詩作常常是讓人讀不懂的&#xff0c;…

lsass.exe 當試圖更新密碼時_“驅動人生”下載器木馬再度更新-你應該注意什么?...

360安全大腦監測到通過"驅動人生"供應鏈攻擊傳播的挖礦木馬在1月30日下午4時左右再次更新。此次更新中&#xff0c;木馬在此前抓取系統帳戶密碼的基礎上增加了抓取密碼hash值的功能&#xff0c;并試圖通過pass the hash攻擊進行橫向滲透&#xff0c;使得該木馬的傳播…

LeetCode【3--無重復的最長字串】 LeetCode【4--有序數組中的中位數】

無重復的最長字串 題目描述 給定一個字符串&#xff0c;請你找出其中不含有重復字符的 最長子串 的長度。 解題思路 看到這道題&#xff0c;其實就兩個步驟&#xff0c;遍歷字符串&#xff0c;記錄當前字符有沒有重復。 重復一般解決就是哈希&#xff0c;這里用個數組表示…

hwt字體轉換ttf_五分鐘教你弄懂了字體反爬是個啥

今天的文章內容主要是關于字體反爬。目前已知的幾個字體反爬的網站是貓眼&#xff0c;汽車之家&#xff0c;天眼查&#xff0c;起點中文網等等。以前也看過這方面的文章&#xff0c;今天跟個老哥在交流的時候&#xff0c;終于實操了一把&#xff0c;弄懂了字體反爬是個啥玩意。…

LeetCode【5--最長的回文子串】 LeetCode【6--Z字形變換】

最長的回文子串 題目描述 給定一個字符串 s&#xff0c;找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。 解題思路 可以跟無重復的最長子串一樣&#xff0c;用一個滑動窗口&#xff0c;只不過這個窗口的右邊界往右&#xff0c;左邊界每回要從右邊界的下標往左…

androidstudio 日歷視圖怎么顯示農歷_中秋國慶旅游攻略怎么做?用這個便簽軟件很簡單...

九月已經到來&#xff0c;中秋節和國慶節距離我們也不遠了&#xff0c;今年的中秋和國慶節重疊了有足足八天的假期。不少人都想趁著這個小長假出門旅游&#xff0c;要想保證旅游質量&#xff0c;那么就要做好攻略。中秋國慶旅游攻略怎么做&#xff1f;要想做好一份中秋國慶旅游…

c++ select函數_PySpark 操作函數一覽

PySpark 操作函數一覽Created: Sep 14, 2020 10:28 AM Tags: Big Data, PySpark, Python, SparkPyspark.sql.functionsfrom pyspark.sql import functions as F函數使用說明基本數學函數類abssin、cos、tan、asin、acos 、atan、sinh、cosh、tanhceil、round、floorexp、log、l…

LeetCode【7--整數反轉】 LeetCode【8--字符串轉整數】

整數反轉 題目描述 給出一個 32 位的有符號整數&#xff0c;你需要將這個整數中每位上的數字進行反轉。 解題思路 x%10 取一位&#xff0c;x/10下一位&#xff0c;注意越界&#xff0c; 代碼實現 class Solution { public:int reverse(int x) {int sum 0;while(x){if(s…

word2003如何設置護眼模式_ERP系統上線,如何設置采購收貨的模式,提升企業的采購效率...

如何合理的規劃采購計劃上次去拜訪一個朋友&#xff0c;他們說公司既然出現沒有下達采購訂單&#xff0c;供應商也有送貨過來的事情&#xff0c;對于公司來說&#xff0c;這個是非常嚴重的問題。若用了ERP系統之后&#xff0c;如何避免類似的事情發生&#xff0c;今天我們來分享…

LeetCode【9-- 回文數】LeetCode【10 --正則表達式的匹配】

回文數 題目描述 判斷一個整數是否是回文數。回文數是指正序&#xff08;從左向右&#xff09;和倒序&#xff08;從右向左&#xff09;讀都是一樣的整數。 解題思路 判斷該數的逆序數是不是和原數相同 代碼實現 class Solution { public:bool isPalindrome(int x) {if(…

sun鍵盤沒有stop鍵_請教Sun鍵盤

請教Sun鍵盤(2011-12-24 06:01:11)標簽&#xff1a;計算機雜談請教Sun鍵盤Sun鍵盤上,Help和F1之間的空白鍵是干啥的?Space旁邊的兩個菱形標志的呢?Compose呢?謝謝!請教Sun鍵盤Space旁邊的兩個菱形標志是一個類似Ctrl、Alt的修飾鍵&#xff0c;叫Meta。可以用Meta;鍵名來定義…