举个例子,我们假设byte[] changeByte(byte oBt)方法就是将原byte转换为用byte[]表示的二进制位,注意:此方法不完善!
*将二进制位转换为byte方法
*oBytes 组成源文件的byte[]
*return:转换后的Byte队列
*/
public byte[] compressor(byte[] oBytes){
byte[] bits = new byte[8];//储存8位二进制位
List<Byte> bL = new ArrayList<Byte>();//转换后长度未知,先用list储存
int bitsLeft = 8;//储存当前bits中剩余位数
for(byte b:oBytes){
byte[] nBits = changeByte(b);//转码,得到二进制位
for(byte oneBit:nBits){
bits[8-bitsLeft]=oneBit;//将新二进制位加入到当前byte的二进制位中
bitsLeft--;//当前byte剩余位数-1
if(bitsLeft==0){//当当前byte填满的时候
try{
byte nByte = changeToByte();
bL.add(nByte);
bitsLeft=8;
}catch(Exception ef){}
}
}
}
}
/**
* 将8位二进制位转换为byte方法,此处用的是位运算
* bits : 储存8个二进制位的byte[]
* return : 转换成的byte
* throws:bits不是8个二进制位格式:不是8位或不止0和1
*/
public byte changeToByte throws Exception(byte[] bits){
if(bits.length!=8){//排除非8位数组的异常
throw new Exception("不是8位!");
}
byte b=0;
for(int i = 0;i<8;i++){
if(bits[i]!=0 && bits[i]!=1){//排除值不为0或1的异常
throw new Exception("值不是0或1")
}
b=(byte)(b<<1);//左移1位
b+=bits[i];
}
return b;
}
相信有些人又发现问题了,最后一个byte编完的时候很可能出现没有补满的情况,那么最后一位怎么填满就成了第二个比较重要的问题了,弄不好就会出现解压时候多了个byte的情况,当然解决的方法也有很多,可以在最前面添加一个参数表示最后一位的空位数然后填满,也可以填入一个编码表中不存在的编码,我采用的是后者,可以节省1个byte的说明。
这样下来,一个文件算是压缩完了,然而不能只压缩不解压,要解压的话,我们需要把编码表存入文件。由于文件长度未知,一般可以保存在文件头部。当然,编码表和正文部分需要用标记来区分开的,我自己是计算出编码表长度在开头用个int值注明,然后读取int个字节作为编码表,后面的就是正文了。当然还有别的方法,就看各位看官自己探索了~~~
下面就是解压了,由于哈夫曼树生成的编码长度的不同,压缩过程中我们需要把8个bit转换为一个byte,c过程就会需要把一个byte拆分为8个bit,方法同样有很多,我这里提供一种方法:
* 将一个byte的bit拆分方法
* b: 需要拆分的byte
* return:一个长度为8的byte[],每个元素储存一位bit
*/
public byte[] getBits(byte b){
byte[] bits = new byte[8];//定义用于储存bit的byte[]
for(int i=0;i<8;i++){
byte t = (byte)(b<<i);//左移i位
t = (byte)(t>>7)//再右移7位,这样最低位就是从高位开始第一位了
t = (byte)(Math.abs(t));//避免负数的影响,主要是当该位为1时
bits[i] = t;//赋值
}
return bits;
}
特别注意的是,将bit[]合并为byte和将byte拆分为bit[]的过程千万不要将byte的高低位弄颠倒!我就犯过这个错误。。找了半天才找到问题。有了这个方法,我们就可以做出解压动作了,将文件中每个byte依次读出(前面的编码表不要再读进来了),将byte拆分为bit[],然后一位一位的对照,就是先取bit[]第一位,查看编码表,没有再取前2位对照,如此反复,找到对应的就储存下来,一个byte读完了读下一个,一直到结束,这个文件就被解压完成了。
后面的扩展就靠各位看官了,比如如何提高压缩/解压效率、压缩多个文件如何实现等,另外,如果修改下这个算法,各byte加权不是由出现次数来定而是事先约定,压缩的时候不将编码表存入文件,那么我们做出来的就是一个加密程序~~