下方鏈接爲用 java 實現哈夫曼樹:
https://blog.csdn.net/www_chinese_com/article/details/88070625
目錄
一、壓縮
利用哈夫曼編碼對文件進行壓縮和解壓的大概步驟如下
(1)讀取文檔中的所有字符,在長度爲256的int型數組(以下取名爲 ascii)中記錄相應字符出現的次數,下標爲字符的ASCII碼值
(2)將數組中值不爲0的項構成二叉樹(權值爲該項的數值,字符爲該項下標ASCII碼值對應的字符),將二叉樹存儲在結點數組中
(3)將結點數字中的結點進行排序並創建一棵哈夫曼樹
(4)爲了能快速找到相應字符所對應的哈夫曼編碼,創建一個哈希表,key 值爲 ascii 數組中值不爲0的爲項的下標,value 爲該 key 值字符的哈夫曼編碼。
(5)根據文件中的字符串提取出每一個字符,在哈希表中找到對應編碼,將編碼以 String 類型進行儲存。
(6)將解壓時需要用到的數據存進壓縮文件中
(7)將得到的編碼,每 32 個字符爲一組,將其裝換爲 int 型數據存入壓縮文件中。如果得到的編碼字符數不是 32 的倍數,可以在編碼的全面或後面補 0 ,再進行上述操作,不過要將補 0 個數寫進文件中。
TreeNode root;// 樹的頭結點
File inputFile;// 要進行壓縮的文件
File outputFile;// 壓縮成的文件
String source = "";// 文件中所有的字符,用於編碼
String allCode = "";// 記錄文件中全部字符的編碼
int[] ascii = new int[256];// 存放str中各個字符出現的次數(除中文外)
ArrayList<TreeNode> array = new ArrayList<TreeNode>();// 將存在的字符的樹節點存在array中,用於排序
HashMap<String, String> map = new HashMap<String, String>();// 存儲字符及其哈夫曼編碼
/**
* 構建一個Haffuman樹
*
* @param str
* 用於構建Huffman樹的數據
*/
public Huffman(File file) {
this.inputFile = file;
collect();// 記錄str中字符的出現次序
setArrayList();// 將其存入鏈表中
sort();// 將其進行排序
// 將鏈表中的結點
TreeNode tNode = null;
while (array.size() > 1) { // 當鏈表中的結點數大於1個的時候,將結點不斷的加入到哈夫曼樹中
tNode = product(array.get(0), array.get(1));
array.remove(0);// 移除原本鏈表中最小的兩個結點
array.remove(0);
array.add(0, tNode);
sort();// 再將新的鏈表進行排序
}
root = tNode;
setHashMap(root, ""); //(4)
getAllCode(); //(5)
saveCode(); // (6) (7)
}
分解步驟:
(1)讀取文檔中的所有字符,在長度爲256的int型數組(以下取名爲 ascii)中記錄相應字符出現的次數,下標爲字符的ASCII碼值
/**
* 堆str中的字符遍歷,ascii存放各個字符出現的次數
*
* @param str
* 用於構建Huffman樹的數據
*/
public void collect() {
Reader reader;
try {
reader = new FileReader(inputFile);
int n = reader.read();// 讀取文件中的字符
while (n != -1) {
char c = (char) n;// 得到文件中字符的ASCII值
source = source + c;
// System.out.println("壓縮類collect中的文件中字符c = " + c + " n = " + n);
ascii[n]++;// 將對應位置的次序加一
n = reader.read();
}
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
(2)將數組中值不爲0的項構成二叉樹(權值爲該項的數值,字符爲該項下標ASCII碼值對應的字符),將二叉樹存儲在結點數組中
/**
* 將ascii中不爲0的數放進鏈表中
*/
public void setArrayList() {
for (int i = 0; i < ascii.length; i++) {// 將ascii數組遍歷一遍
if (ascii[i] != 0) { // 當其中的值不爲0
TreeNode tNode = new TreeNode(new Node(ascii[i], (char) i + ""));
array.add(tNode);
}
}
}
(3)將結點數字中的結點進行排序並創建一棵哈夫曼樹
/**
* 根據權值(鏈表中存放的數據),將ascii代表的字符從小到大進行排序
*/
public void sort() {
// 認爲i前面的數據都是有序的,從第二個數開始遍歷,
// 記住開始的位置,記住開始的結點,當遇到第一個比它小的數時,
// 檢驗此時的位置j是否和開始的位置相同,若相同,則不用變化
// 若不相同,刪除上一次的位置,將這個結點插入到現在這個位置
for (int i = 1; i < array.size(); i++) {
int min = i;
int j;
TreeNode tNode = array.get(i);
for (j = i; j > 0; j--) {
TreeNode lastNode = array.get(j - 1);
if (tNode.node.pow > lastNode.node.pow)
break;// 找到第一個比他小的數據時。退出循環
}
min = j;
if (min != i) {// 如果,min值發生變化,即前面有比pow大的數
array.remove(tNode);
array.add(min, tNode);
}
}
}
(4)爲了能快速找到相應字符所對應的哈夫曼編碼,創建一個哈希表,key 值爲 ascii 數組中值不爲0的爲項的下標,value 爲該 key 值字符的哈夫曼編碼。
/**
* 得到每一個字符對應的哈夫曼編碼,再將編碼存儲到HashMap中(遞歸)
*
* @param tNode
* 根結點
* @param code
* 這個結點對應的編碼
*/
public void setHashMap(TreeNode tNode, String code) {
if (tNode.left != null) {
setHashMap(tNode.left, code + "0");
}
if (tNode.right != null) {
setHashMap(tNode.right, code + "1");
}
if (tNode.left == null && tNode.right == null)
map.put(tNode.node.c, code);
}
(5)根據文件中的字符串提取出每一個字符,在哈希表中找到對應編碼,將編碼以 String 類型進行儲存。
/**
* 得到文件中全部字符的全部編碼
*/
public void getAllCode() {
// 將在Huffman中得到的文件的內容全部翻譯爲編碼
for (int i = 0; i < source.length(); i++) {
String key = source.charAt(i) + "";// 得到每一個字符
String value = map.get(key);// 得到每一個字符的編碼
allCode = allCode + value;
}
}
(6、7)儲存相關數據到壓縮文件中
/**
* 將哈夫曼編碼存起來
*/
public void saveCode() {
// 存入字符及字符對應的編碼, 存入補零個數,存入所有字符的編碼
outputFile = new File("src\\Tree\\SaveInformation");
OutputStream out;
try {
// 創建輸出流對象
out = new FileOutputStream(outputFile);
DataOutputStream dout = new DataOutputStream(out);
// 將鍵值數寫進壓縮文件中
int size = map.size();// map中的鍵值數
dout.writeInt(size);
// 將HashMap寫進文件中
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {// 得到map中的內容
String key = (String) iterator.next();
String value = map.get(key);
dout.writeUTF(key);
dout.writeUTF(value);
}
int addZero = 0;// 添加0的個數
if (allCode.length() % 32 != 0) {
addZero = 32 - allCode.length() % 32;
for (int i = 0; i < addZero; i++)// 給得到的編碼補零(在前面加上)
allCode = "0" + allCode;
}
// 寫入補零個數
dout.writeInt(addZero);
// 每32位爲一個int進行輸入
for (int i = 0; i < allCode.length(); i += 32) {
char[] dst = new char[32];
allCode.getChars(i, i + 32, dst, 0);// 得到32個字符形成的字符數組 (用這個方法不可以,出錯了)
// for (int j = 0; j < 32; j++) { //依次取出32位編碼
// dst[j] = allCode.charAt(i + j);
// }
// 對字符數組進行處理,算出int值,寫入文件
// int型第0位爲符號位,爲避免出錯,從第1位開始
int intCode = 0;
for (int j = 1; j < 32; j++) {
intCode = intCode * 2;
intCode = intCode + (dst[j] - '0');
}
//對0號位上的字符進行判斷,決定int型數據的正負
if (dst[0] == '1')
intCode = -intCode;
dout.writeInt(intCode);
}
dout.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
二、解壓
由於在壓縮是利用數據流將數據存進文件中,因此同樣的,用數據流將數據從壓縮文件中讀取出來,再將編碼翻譯成字符串即可。
File sourceFile;// 需要解壓的文件
File objectFile;// 目標文件
DataInputStream din;// 輸入流
Writer writer;// 字符輸出流
// 讀取文件中HashMap中的內容,得到字符及其相應的編碼,但是key值爲編碼,value值爲字符
HashMap<String, String> map = new HashMap<String, String>();
public Decompress(File sourceFile) {
this.sourceFile = sourceFile;// 得到目標文件
try {
din = new DataInputStream(new FileInputStream(sourceFile));// 實例化輸入流對象
objectFile = new File("src\\Tree\\DecompressingFile");// 構建解壓的目標文件
writer = new FileWriter(objectFile);// 實例化輸出流對象
operate(); //進行解壓的操作方法
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
按存入的順序將數據讀取出來,在這裏,我們同樣需要將哈希表讀取出來並創建一張新的哈希表,但是新創建的這張哈希表,key 值爲字符的編碼,value 值爲字符,因爲此時我們是通過編碼找到字符。
/**
* 得到HashMap表
*/
public void getHashMap() {
try {
int size = din.readInt();// 得到map的鍵值數
for (int i = 0; i < size; i++) {
String value = din.readUTF();// 得到字符
String key = din.readUTF();// 得到字符對應的編碼
map.put(key, value);
}
} catch (IOException e) {
e.printStackTrace();
}
}
將讀取出來的 int 數據轉變爲二進制的方法
public int[] change(int num) {
int[] array = new int[32];// 將byte化爲二進制後的結果存入這個數組中
if (num < 0)
array[0] = 1;
else
array[0] = 0;
num = Math.abs(num);// 得到正數
for (int i = 31; i > 0; i--) {
array[i] = num % 2;
num = num / 2;
}
return array;
}
進行解壓的相關操作的方法