在我多年的开发经验中,经常发现的一个情况就是,很多项目的对象字段或者是数据库字段本来是数字类型的,却被定义成字符串类型,这无关痛痒吗?
对于小项目来说,可能没什么影响,反正只要业务逻辑正确即可,性能没什么问题,因为数据也不多,用户也不多。
然而,对于大数据处理来说,这个可不是小事,从字符串替换为数字类型,可以极大地节省内存、磁盘存储以及网络带宽,减少IO的代价,而且很多数据结构和算法使用数字类型比字符串要更快。
我们来看一个例子,假设你有很多的日志需要处理,而每条日志都有一个唯一的标识,标识类似这样的格式:
F5051582611729507844 3832154813577306424 F1624235934976711017 3810376634214027595 F6884923813121317381 7278044081826528150
看到这些标识,你怎么想?我的第一反应应该是数字,可是怎么有个F呢?我想可以把它当做16进制。后来发现可以把F当做负号,这就是一个64位的长整型。
那么如果你把这些标识当成字符串,会有什么不同呢?
当然有,如果你每秒要处理这样的日志百万或者千万条,每条处理结果可能会包含百万或者千万个这样的标识元素构成的集合,这个不同就会体现的非常明显。
下面,我们来分析一下标识3832154813577306424的存储占用情况:
1、内存占用
当做字符串:我们知道,JAVA中字符串是由字符构成的,一个字符是由2个字节构成的(这是JAVA的悲剧了),上述标识有19个字符,所以,占用的内存大小为:19*2+4=42(字节),+4是因为字符串使用一个整型保存字符串的哈希值。
当做数字:如当做长整型,则占用的内存大小为8字节。
这里有5倍以上的差距了吧。
2、序列化字节大小
当我们需要通过网络传输这些标识或者需要把这些标识存储到磁盘中的时候,我们就需要把这些标识转换为字节数组,如何转换为字节数组呢?我们可以使用多种编码方式。
当做字符串:我们知道,JAVA中字符串转换为字节数组可以使用多种编码方式,我们看看常见的编码方式对如上字符串编码之后的字节数:
String abc = "3832154813577306424"; System.out.println("3832154813577306424 length:"+abc.length()); System.out.println(Charset.defaultCharset().name()+":"+abc.getBytes().length); System.out.println("unicode:"+abc.getBytes("unicode").length); System.out.println("gbk:"+abc.getBytes("gbk").length); System.out.println("gb2312:"+abc.getBytes("gb2312").length); System.out.println("ISO-8859-1:"+abc.getBytes("ISO-8859-1").length);
输出如下:
3832154813577306424 length:19 UTF-8:19 unicode:40 gbk:19 gb2312:19 ISO-8859-1:19
当做数字:如当做长整型,则占用的内存大小为8字节。
这里有2倍以上的差距了吧。
那么我们如何在长整型和字节数组之间转换呢?
String abc = "3832154813577306424"; System.out.println("3832154813577306424 length:"+abc.length()); System.out.println("long:"+ByteUtils.longToBytes(Long.parseLong(abc)).length); byte[] bytes = ByteUtils.longToBytes(Long.parseLong(abc)); System.out.println("string:"+ByteUtils.bytesToLong(bytes));
输出如下:
3832154813577306424 length:19 long:8 string:3832154813577306424
public static byte[] longToBytes(long x) { ByteBuffer longBuffer = ByteBuffer.allocate(Long.BYTES); longBuffer.putLong(0, x); return longBuffer.array(); } public static long bytesToLong(byte[] bytes) { return bytesToLong(bytes, 0, bytes.length); } public static long bytesToLong(byte[] bytes, int offset, int length) { ByteBuffer longBuffer = ByteBuffer.allocate(Long.BYTES); longBuffer.put(bytes, offset, length); longBuffer.flip();//need flip return longBuffer.getLong(); }