java数字字符串压缩

跟同事讨论一个问题,将20个十进制数的数字字符组成的字符串尽量压缩。
我首先想到从bit位层面考虑压缩,毕竟压缩后的东西一般不能直接用于表达,只是用于传输或存储。java的一个字符占2字节16bit位,但表示一个十进制数只需要4bit位就够了。在这种思路下,采用位截断压缩,可以把四个字符压缩到一个字符中。

public class CompressDigitalString { 

    //以String中的char元素为一个基本单位,16bits,存储4个数字的4bits。以下数组用空间换时间,分别构造出数字0~9的4bit位存储模型
    static final char LOW_LOW_BYTES[] = {
  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009};    //最右边的4位
    static final char LOW_HIGH_BYTES[] = {
  0x0000, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080, 0x0090};    //倒数第二个右边的4位
    static final char HIGH_LOW_BYTES[] = {
  0x0000, 0x0100, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600, 0x0700, 0x0800, 0x0900};    //倒数第三个右边的4位
    static final char HIGH_HIGH_BYTES[] = {
  0x0000, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0x7000, 0x8000, 0x9000};    //最左边的4位
    static final char BYTES[][] = {HIGH_HIGH_BYTES, HIGH_LOW_BYTES, LOW_HIGH_BYTES, LOW_LOW_BYTES};    //为了方便做循环,将上面的数组拼在一起

    public static String compress(String num) {
        int size = num.length();
        size = ( size & 0x03 ) != 0 ? size >> 2 + 1 : size >> 2;    //size&0x03表示对4取模,判断是否整除4。计算最后返回String类型字符长度
        StringBuilder temp = new StringBuilder(size);
        char c = 0x0000;
        for(int i = 0; i < num.length(); ++i) {    //对String从左到右处理,左边的可以认为是字串的高位
            if ((i & 0x03) == 0 && i != 0) {    //每处理完String的4个字符就做一次拼接,但最后一次的此处拼接不了
                temp.append(c);
                c = 0x0000;    //恢复到原始状态,等待下一次拼接
            }
            c |= BYTES[i & 0x03][Integer.valueOf(String.valueOf(num.charAt(i)))];    //将逐数字在各自的位模型用 '或' 操作
        }
        return temp.append(c).toString();    //处理最后一次拼接,然后转化成String并返回
    }
    //输入字符串为 "1234",计算后返回字符串的内存结构(16进制)为0x1234,也即是 0001 0010 0011 0100,但是这个字符不可见...
}

这是一种非常简单原始的想法,需要注意,由于没有对原始串的长度计数,复原时不知道该恢复成多少位。但作为如此简单而且压缩效率尚可的方式,这个方法值得改进后使用。

后来同事又提出来一个要求,压缩出来的字符串不能太任意,字符必须在0~9a~zA~Z这62个字符中。然后我就犯难了。上面描述的方式相当于每个char能够使用10^4种形态(每4bit可以有0~9共10种形态),而这个要求使得每个char只有62种形态,就压缩程度说来要大打折扣。进制转换的思路能够比较容易实现这个需要,但压缩出来的字符串长度会比较大:

log(10^20)/log(62)=11.1582...

需要12位,而且还需要自己实现10进制和62进制的转化。有个便利的做法,利用java内置的BigIngeter来完成10进制数转化36(0~9a~z)进制数。

    BigInteger test = new BigInteger("12313213");
    String ret = test.toString(36);
    //压缩位数 log(10^20)/log(36)=12.8509...

从压缩结果上看,只相差1位,但是编码量会小很多,而且不容易出错。

    原文作者:阿奴波仔
    原文地址: https://blog.csdn.net/xiangyubobo/article/details/48802471
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞