VB(Variable byte, 可变字节)编码利用整数个字节来对间距编码。字节的后7 位是间距的有效编码区,而第1 位是延续位(continuation bit)。如果该位为1,则表明本字节是某个间距编码的最后一个字节,否则不是。要对一个可变字节编码进行解码,可以读入一段字节序列,其中前面的字节的延续位都为0,而最后一个字节的延续位为1。根据上述标识可以把每个字节的7 位部分抽取出来并连接在一起形成编码。图5-8 给出了VB编码和解码的伪代码,表5-4 给出了一个采用VB编码的例子①。
图5-8 采用VB 的编码和解码过程。div 和mod 函数分别计算整数相除和相除后的
余数。PREPEND 在表头部增加一个元素。比如 PREPREND((1,2),3)=(3,1,2)。
EXTEND 表示将表合并,比如,EXTEND((1,2),(3,4))=(1,2,3,4)
代码实现:
package test3;
import java.math.BigInteger;
import java.util.LinkedList;
public class VariByteCode {
public static void main(String[] args){
int n=824;
System.out.println("测试的间隔大小为"+n);
LinkedList<String> byteStream=VBEncode(n);
System.out.println("可变字节码的二进制形式:"+byteStream); //输出VB编码
int number=VBDecode(byteStream);
System.out.println("由可变字节码形式转化为十进制:"+number); //输出解码后的结果
n=5;
System.out.println("测试的间隔大小为"+n);
byteStream=VBEncode(n);
System.out.println("可变字节码的二进制形式:"+byteStream); //输出VB编码
number=VBDecode(byteStream);
System.out.println("由可变字节码形式转化为十进制:"+number); //输出解码后的结果
n=214577;
System.out.println("测试的间隔大小为"+n);
byteStream=VBEncode(n);
System.out.println("可变字节码的二进制形式:"+byteStream); //输出VB编码
number=VBDecode(byteStream);
System.out.println("由可变字节码形式转化为十进制:"+number); //输出解码后的结果
}
public static LinkedList<String> VBEncode(int n){ //我觉得这个算法只能用作自然数的压缩
LinkedList<String> byteStream=new LinkedList<>(); //创建list存放可变字节码的二进制形式
LinkedList<Integer> bytes=new LinkedList<>(); //创建list存放每一步的十进制结果
//这里把伪代码中的VBEncodeNumber和VBEncode合成一步
while(true){ //将运算后的结果放入bytes中,如214577运算后的结果为13,12,49
bytes.add(0,(n%128));
if(n<128)
break;
n/=128;
}
int lastIndex=bytes.size()-1; //得到bytes最后一个数的下标
int lastNumber=bytes.getLast(); //得到bytes最后一个数
bytes.set(lastIndex, lastNumber+128); //lastNumber+128,相当于将延续位变为1,如214577运算后的结果为13,12,177
for(int n1:bytes){
byteStream.add(toBinary(n1)); //将bytes中的每一个数字变成8位的二进制字符串,并存入byteStream中
}
//为了结果方便阅读,这里就不把byteStream中的数据进行合并了
/*
* 合并
* String VBCode="";
* for(String str:byteStream){
* VBCode+=str; //VBCode为合并后的结果
* }
*/
return byteStream; //返回byteStream
}
public static int VBDecode(LinkedList<String> bytestream){
LinkedList<Integer> numbers=new LinkedList<>(); //创建list存放由可变字节码的二进制形式得到十进制数字
for(String s:bytestream){ //获取byteStream中的每一个二进制字符串
BigInteger src = new BigInteger(s, 2);
int number=Integer.parseInt(src.toString()); //将二进制字符串变成十进制
if(number<128) //如果小于128直接放入numbers中,否则减去128再放入,相当于把延续位改为0
numbers.add(number);
else
numbers.add(number-128);
}
int result=0;
int power=numbers.size()-1; //幂次数
for(int i=0;i<numbers.size();i++){
result+=numbers.get(i)*Math.pow(128, power);
power--; //幂次数--
}
return result; //返回结果
}
public static String toBinary(int n){
int[] byteArray = new int[8]; //新建8位数组
String binary = Integer.toBinaryString(n);
for(int i=0;i<8-binary.length();i++)
byteArray[i]=0; //补足0
for(int i=0;i<binary.length();i++)
byteArray[i+8-binary.length()]=binary.charAt(i)-48; //将binary放入数组的后面
String result="";
for(int n1:byteArray){ //将数组转为字符串
result+=n1;
}
return result; //返回字符串
}
}
结果为:
测试的间隔大小为824
可变字节码的二进制形式:[00000110, 10111000]
由可变字节码形式转化为十进制:824
测试的间隔大小为5
可变字节码的二进制形式:[10000101]
由可变字节码形式转化为十进制:5
测试的间隔大小为214577
可变字节码的二进制形式:[00001101, 00001100, 10110001]
由可变字节码形式转化为十进制:214577
采用VB 编码压缩,我们在实验中发现,Reuters-RCV1 语料库的索引可以压缩到116MB,
相对于未压缩的索引,压缩率超过了50%
VB 编码的思想也可以应用于比字节更大或更小的单位上,比如32 比特位字、16 比特位字
和4 比特位字(nibble,也称半字节)等等。编码单位越长,那么所需的位操作次数也越少,但
是同时压缩率会降低甚至没有压缩。更短的编码单位会得到更高的压缩率,但同时位操作的次
数也越多。总而言之,以字节为单位在压缩率和解压缩的速度之间提供了一个很好的平衡点。