关于Map的优化
Map(HashMap)是Java中最常用的一种数据结构,灵活性好,性能比较均衡。
提到HashMap的性能,就会想到初始化Capacity,减少数据复制的开销。
下面我们换一个新的角度来谈谈HashMap的性能优化。
场景
绝大多数性能优化,都是基于一个特定的业务场景,才会有比较明显的效果。
我们平时使用HashMap承载数据的时候,通常Key的个数和种类都是非常有限的。
比如,Key的可选项有8个,大多数会使用其中的六七个,而Value几乎每次都不一样。
具体到一些业务场景,比如: 1、message的header 2、record的tag 3、interface的params、result
如果用具体的Class作为数据的实体,会有比较高的数据“密度”,读写性能也更高。
当然在一些开源框架上(比如Flume的EventHeader),就会使用HashMap来承载数据。
如果系统中使用的这种HashMap非常多,比如一秒钟要创建和使用几万、几十万个Map,
就可以参考一下,下面的优化方案了。
Java对象的内存布局
首先温习一下Java对象的内存布局,这里不再详细讲解了,注意UseCompressedOops的作用。
再看看HashMap的源码,核心是一个Node的数组,Node中包括hash、key、value、next指针。
上面设定的业务场景中,Key是具有极大的公用性的,如果把行结构转成列结构,就可以共用了。
如果列化共用之后,Map的Key部分的内存开销是一个常数,一个对象引用的大小。
如果Key的使用率太低的话(10个key只有其中的1-2个被写入),也会造成比较大的浪费。
Hash
Hash是一个数学运算过程,属于CPU密集型,也是性能优化中比较难处理的一类问题。
因为场景中Key的集合相对固定,这里可以考虑彻底放弃Hash,直接使用常量列表,
把用Hash来定位数据,替换成直接使用数组的下标,这样开销应该是降低到了极限了。
为了保持和Map的兼容性,也要提供根据key来获取val的功能,这样更容易融合进框架中。
EntrySet
HashMap的Node实现了Map.Entry,所以遍历Map的过程就是遍历Node数组加Node链表。
前面提到列化、key和value都是在数组中的,所以遍历也就是数组的遍历过程,更加简单一点。
实现
测试
简单的创建和读写Map来进行测试,ArrayMap比HashMap要快一倍左右。
设置128M的JVM进行内存布局的测试,ArrayMap的大小是HashMap的四分之一左右。