ハフマン木をバイナリコードに変換する
前回のブログの最後で、ハフマン木は で01
構成される文字列に変換されましたが、これは明らかに実際のアプリケーションでは操作ではありません。実際に必要なのは01
ビットの文字列です。例: 字符"A" 编码:0100 0001(8bit),假设我们重新编码之后字符 "A" 的路径是01,只占2个bit,应该用 01(bit)表示,而不是字符类型的"01"
、文字タイプ 01 を使用して再エンコードした場合、ハフマン エンコード後のデータは元のデータより大きくなります。したがって、それを改善する必要があります。(追記: ハフマン木の原理が分からない方は、まず前回のブログを読んでください。)
テキストに多くの文字が含まれている場合、ルート ノードからリーフ ノードまでのパスが非常に長くなる可能性があります。バイナリ パスが int 変数に格納され、パスの長さが 32 ビットを超える場合、int 変数はパスを完全に格納できません。したがって、パスの保存には配列のみが使用でき、byte、short、int、long が使用できます。適度な int[] または他の型を選択してください。メモリを最も節約できる byte[] を使用するのが最善ですが、この記事ではパスを保存するために long[] 型を使用します。(完全なコードアドレス)
//统计字符使用频率和构建Huffman树的代码在上一篇文章中贴出来了,不再重复粘贴;完整代码在链接中;
public void bitCode(HMNode root,Map<Character,long[]> huffmanCode,String code){
if(root.leaf){
System.out.println(root.str+ " -> " + code);
huffmanCode.put(root.str,convertBits(code));
return;
}
if(root.left != null){
bitCode(root.left,huffmanCode,code+"0");
}
if(root.right != null){
bitCode(root.right,huffmanCode,code+"1");
}
}
//将字符串转化成bits
public long[] convertBits(String code){
int len = code.length() ;
int codeLength = len% 64 ==0 ? len/64:len/64+1;
int index = 1;
int offset = 63;
long[] bitCodes = new long[codeLength+1];
char[] codes = code.toCharArray();
for (int i = 0; i < codes.length; i++) {
long bitCode = bitCodes[index];
long c = codes[i]-'0';
bitCode |= (c<<offset);
bitCodes[index]=bitCode;
if(offset== 0){
offset = 63;
index += 1;
}else{
offset -= 1;
}
}
bitCodes[0]=(long)len;//第一个数字是用来记录编码的长度;
return bitCodes;
}
テスト:
@Test
public void test(){
String text = "aaabbbbbccccccddddee";
HMNode root =buildHuffManTree(text);
Map<Character,long[]>code = new HashMap<>();
bitCode(root,code,"");
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++");
code.forEach((key,val)->{
long codeLen = val[0];
System.out.print(key+"编码长度:"+codeLen +"\tcode: ");
for (int i = 1; i < val.length-1; i++) {
System.out.print(" "+val[i]);
}
System.out.print(" "+(val[val.length-1]>>>(64-codeLen)));
System.out.println("\n");
});
}
=========================================res=================================================
d -> 00
c -> 11
b -> 01
e -> 100
a -> 101
+++++++++++++++++++++++++++++++++++++++++++++
d编码长度:2 code: 0
b编码长度:2 code: 1
a编码长度:3 code: 5
c编码长度:2 code: 3
e编码长度:3 code: 4
テキスト圧縮
EncodeInfo は圧縮テキストを保存し、それを 2 次元配列に設定します。(圧縮しながらディスクに保存する必要があるため、今は考える必要はありません。データを完全に圧縮して保存することは不可能です。)
テキストをトラバースすると、各文字のハフマン バイナリ コード配列が圧縮テキストの 2 次元配列に順番に保存されます。これには、低位ビットの操作に少し慣れている必要があります。(2桁の配列で保存する必要はありません。後で変更しましょう)
class EncodeInfo{
List<List<Long>> encodes = new ArrayList<>();
int lastLength = 0;//encodes最后一个list的最后一个long元素中,有效bit个数
HMNode root;
public EncodeInfo(HMNode root){
List<Long> code = new ArrayList<>(ARRAY_SIZE);
code.add(0L);
encodes.add(code);
this.root = root;
}
}
static int ARRAY_SIZE = 1024;
static int BIT_CELL_LENGTH = 64;
public void encode(String text,EncodeInfo info){
Map<Character,long[]>code = new HashMap<>();
bitCode(info.root,code,"");
for (char c : text.toCharArray()) {
addBitCode(info,code.get(c));
}
}
private void addBitCode(EncodeInfo info ,long[] bits){
List<List<Long>> encodes = info.encodes;
List<Long> codes = encodes.get(encodes.size()-1);//获取到最后一个List;
//得到二进制码数组最后一个元素的有效bit数;
long last = bits[0]%BIT_CELL_LENGTH == 0 ? BIT_CELL_LENGTH : bits[0] % BIT_CELL_LENGTH;
for (int i = 1; i < bits.length-1; i++) {
long b = bits[i];
int size = codes.size();
long val = codes.get(size-1) ;
val |= (b >>> info.lastLength);//前半部分
codes.set(size-1,val);
long after = b << (BIT_CELL_LENGTH-info.lastLength);//后半部分
codes = addLast(info,after);
info.lastLength = info.lastLength == 0 ? 0: BIT_CELL_LENGTH-info.lastLength;
}
long lastBits = bits[bits.length-1];
long val = codes.get(codes.size()-1);
val |= (lastBits >>> info.lastLength);
codes.set(codes.size()-1,val);
if(last + info.lastLength < BIT_CELL_LENGTH){
info.lastLength += last;
}else if(last + info.lastLength == BIT_CELL_LENGTH){
info.lastLength =0;
addLast(info,0L);
}else{
lastBits<<= (BIT_CELL_LENGTH-info.lastLength);
addLast(info,lastBits);
info.lastLength +=(last-BIT_CELL_LENGTH);
}
}
private List<Long> addLast(EncodeInfo info,long val){
List<List<Long>> encodes = info.encodes;
List<Long> codes = encodes.get(encodes.size() - 1);
int size = codes.size();
if(size == ARRAY_SIZE){
codes = new ArrayList<>(ARRAY_SIZE);
encodes.add(codes);
}
codes.add(val);
return codes;
}
- テスト
@Test
public void testEncode(){
String text = " sfsdftechfdfhgjhtghrytthnhGGHMBDV打发士大夫SDCYUGERFASDSDW,.*&^%$#[]_+{};;;;;,.XCCCCC地方法规";
int beforeEncode = text.getBytes().length;
System.out.println("编码前:beforeEncode:"+beforeEncode +";");
HMNode root =buildHuffManTree(text);
EncodeInfo info = new EncodeInfo(root);
encode(text,info);
info.encodes.forEach(x->{
for (Long aLong : x) {
System.out.println(aLong);
}
});
System.out.println("最后一个long的有效bit个数:"+info.lastLength);
}
- 結果
编码前:beforeEncode:103;
* -> 000000
夫 -> 000001
+ -> 000010
地 -> 000011
方 -> 000100
A -> 000101
B -> 000110
规 -> 000111
E -> 001000
F -> 001001
H -> 001010
M -> 001011
发 -> 001100
R -> 001101
打 -> 001110
U -> 001111
法 -> 010000
V -> 010001
W -> 010010
X -> 010011
Y -> 010100
[ -> 010101
] -> 010110
^ -> 010111
_ -> 011000
c -> 011001
e -> 011010
j -> 011011
士 -> 011100
n -> 011101
r -> 011110
y -> 011111
; -> 1000
{
-> 100100
} -> 100101
G -> 10011
C -> 1010
h -> 1011
S -> 11000
D -> 11001
f -> 11010
t -> 11011
, -> 111000
. -> 111001
d -> 111010
g -> 111011
s -> 111100
-> 1111010
# -> 1111011
$ -> 1111100
% -> 1111101
& -> 1111110
大 -> 1111111
-727686570736772137
6538905317935103933
-2614557749356706589
2089122890596168205
2620082441562243324
-4620800053492013930
2459590903680641548
4901886719416074240
最后一个long的有效bit个数:16
この数値は、テキストがハフマン エンコーディングによって圧縮された後に保存される情報です。各 Long には 8 バイトの情報が保存されます。圧縮されたバイト数は、8 * 7 + 16 /8 = 58 と計算できます。元の 103 より少し小さくなります。本文には基本的に繰り返しがないため、圧縮効果はあまり良くありません。
テキスト解凍
解凍するときは各ビットを順番に読み込んでいき、ハフマン木でいうと1なら右の枝へ、0なら左の枝へ進みます。(これは、以前のエンコード方法に依存します。ハフマン ツリーを構築するときに 1 が左を意味する場合、0 が右を意味する場合は、対応する方法で解析します。) リーフ ノードが見つかると、文字が出力され、すべてのビットが読み取られるまで連続してビットを読み取り続けます。
- コード
public String decode(EncodeInfo info){
List<List<Long>> encodes = info.encodes;
int endListIndex = encodes.size()-1;
int endElementIndex = encodes.get(endListIndex).size()-1;
HMNode cur= info.root;
for (int i = 0; i < encodes.size(); i++) {
List<Long> codes = encodes.get(i);
for (int j = 0; j < codes.size(); j++) {
if(i == endListIndex && j == endElementIndex){
cur= parse(cur,codes.get(j),info.lastLength,info);
}else{
cur= parse(cur,codes.get(j),BIT_CELL_LENGTH,info);
}
}
}
return null;
}
/**
* @param cur
* @param bits
* @param len long型变量的有效bit长度;
* @param info
* @return
*/
public HMNode parse(HMNode cur,long bits,int len,EncodeInfo info){
long curBit = 0;
while(cur != null && len > 0){
curBit = (bits>>>(BIT_CELL_LENGTH-1));
if(curBit == 1 ){
cur= cur.right;
}else{
cur= cur.left;
}
bits<<=1;
len--;
if(cur.leaf){
System.out.print(cur.str);
if(len ==0)
return null;
else
break;
}
}
if(len == 0)return cur;
return parse(cur=info.root,bits,len,info);
}
- テスト
@Test
public void testDecode(){
String text = "急急急uu ,.;/.'*&^%$广东福建的好的的德国费尔法的的发夫算法发生过和323434234123吧vDVD产生的";
int beforeEncode = text.getBytes().length;
System.out.println("编码前:beforeEncode:"+beforeEncode +";");
HMNode root =buildHuffManTree(text);
EncodeInfo info = new EncodeInfo(root);
encode(text,info);
decode(info);
System.out.println("\n+++++++++++++++");
System.out.println(text);
}
- 結果
编码前:beforeEncode:121;
的 -> 000
2 -> 0010
4 -> 0011
广 -> 01000
D -> 01001
发 -> 01010
法 -> 01011
生 -> 01100
-> 01101
. -> 01110
u -> 01111
过 -> 100000
和 -> 100001
福 -> 100010
尔 -> 100011
V -> 100100
算 -> 100101
东 -> 100110
^ -> 100111
$ -> 101000
% -> 101001
& -> 101010
' -> 101011
吧 -> 101100
产 -> 101101
* -> 101110
夫 -> 101111
, -> 110000
/ -> 110001
1 -> 110010
v -> 110011
德 -> 110100
费 -> 110101
建 -> 110110
; -> 110111
3 -> 1110
好 -> 111100
国 -> 111101
急 -> 11111
急急急uu ,.;/.'*&^%$广东福建的好的的德国费尔法的的发夫算法发生过和323434234123吧vDVD产生的
+++++++++++++++
急急急uu ,.;/.'*&^%$广东福建的好的的德国费尔法的的发夫算法发生过和323434234123吧vDVD产生的