標準工具(GZIP 等)針對通用數據優化,對於短文本、字符分布極不均勻、特定業務字符集場景,哈夫曼編碼往往能產出更小體積,且無第三方依賴。
public class Node implements Comparable<Node> {
int weight;
Node left, right;
String huffCode;
boolean isLeaf;
Character value;
public Node(Character value, int weight) { // 葉子
this.value = value; this.weight = weight; this.isLeaf = true;
}
public Node(int weight, Node left, Node right) { // 內部節點
this.weight = weight; this.left = left; this.right = right; this.isLeaf = false;
}
public void incWeight(int i) { weight += i; }
@Override public int compareTo(Node o) { return this.weight - o.weight; }
}
public class HuffmanTree {
private Map<Character, Node> leafMap = new HashMap<>();
private List<Node> nodes = new ArrayList<>();
// 編碼入口
public String encode(String text) {
if (text == null) return null;
buildWeights(text);
Node root = buildTree();
genCodes(root, "");
StringBuilder sb = new StringBuilder();
for (char ch : text.toCharArray()) sb.append(leafMap.get(ch).huffCode);
return sb.toString(); // 如 "010011..."
}
// 解碼入口
public String decode(Map<String, Character> codeTable, String bits) {
StringBuilder sb = new StringBuilder();
int start = 0, end = 1;
while (end <= bits.length()) {
String key = bits.substring(start, end);
if (codeTable.containsKey(key)) {
sb.append(codeTable.get(key));
start = end;
}
end++;
}
return sb.toString();
}
// 獲取編碼表 (傳輸給解碼端)
public Map<String, Character> getCodeTable() {
Map<String, Character> map = new HashMap<>();
leafMap.forEach((ch, node) -> map.put(node.huffCode, ch));
return map;
}
// --- 內部實現 ---
private void buildWeights(String text) {
for (char ch : text.toCharArray()) {
leafMap.computeIfAbsent(ch, c -> {
Node n = new Node(c, 1);
nodes.add(n);
return n;
}).incWeight(1);
}
}
private Node buildTree() {
Collections.sort(nodes);
while (nodes.size() > 1) {
Node l = nodes.remove(0);
Node r = nodes.remove(0);
insertSorted(nodes, new Node(l.weight + r.weight, l, r));
}
return nodes.remove(0);
}
private void insertSorted(List<Node> list, Node node) {
int lo = 0, hi = list.size();
while (lo < hi) {
int mid = (lo + hi) >>> 1;
if (node.weight < list.get(mid).weight) hi = mid;
else lo = mid + 1;
}
list.add(lo, node);
}
private void genCodes(Node node, String prefix) {
if (node.isLeaf) { node.huffCode = prefix; return; }
genCodes(node.left, prefix + "0");
genCodes(node.right, prefix + "1");
}
}
public class HuffUtils {
// "010011" → byte[]
public static byte[] toBytes(String bits) {
int len = bits.length();
byte[] bytes = new byte[(len + 7) / 8];
for (int i = 0; i < len; i++) {
if (bits.charAt(i) == '1') bytes[i / 8] |= 1 << (7 - i % 8); // 大端序
}
return bytes;
}
// byte[] → "010011..." (需知最後一字節有效位數)
public static String toBits(byte[] bytes, int lastValidBits) {
StringBuilder sb = new StringBuilder(bytes.length * 8);
for (int i = 0; i < bytes.length; i++) {
int b = bytes[i];
int valid = (i == bytes.length - 1) ? lastValidBits : 8;
for (int bit = 7; bit >= 8 - valid; bit--) {
sb.append((b >> bit) & 1);
}
}
return sb.toString();
}
}
public class HuffmanTest {
public static void main(String[] args) {
String original = "abcdacaha"; // 測試文本
HuffmanTree tree = new HuffmanTree();
// 壓縮端
String bits = tree.encode(original);
byte[] bytes = HuffUtils.toBytes(bits);
Map<String, Character> codeTable = tree.getCodeTable();
int lastBits = bits.length() % 8 == 0 ? 8 : bits.length() % 8;
System.out.println("原文: " + original);
System.out.println("編碼: " + bits);
System.out.println("字節數: " + bytes.length + " (原始 " + original.getBytes().length + " 字節)");
System.out.println("編碼表: " + codeTable);
// 模擬傳輸...
// 解壓端
String decodedBits = HuffUtils.toBits(bytes, lastBits);
String restored = tree.decode(codeTable, decodedBits);
System.out.println("還原: " + restored);
System.out.println("驗證: " + original.equals(restored));
}
}
原文: abcdacaha
編碼: 0100110111001011
字節數: 2 (原始 9 字節)
編碼表: {0=a, 100=b, 101=c, 110=d, 111=h}
還原: abcdacaha
驗證: true
lastValidBits 避免解碼多出垃圾字符int[] 代替 Map 統計 ASCII/GBK 字符;位操作用 BitSet 或手寫位流