Skip to content

性能微优化-Array和Map

About 780 wordsAbout 3 min

性能新书

2026-08-20

数组和Map

数组和HashMap,这俩种数据结构能存取数据集合,前者性能都能极致到O(1),然而Java中的HashMap的性能理想情况下是O(1),在有HASH冲突情况下是O (logn)。 另外考虑到HashMap 实现功能前必须获取Key的hashCode。因此在需要高性能应用场景里,建议使用数组存取对象而不是用HashMap。

比如脚本引擎实现,如何保存脚本引擎的变量,有俩种方式,一种使用HashMap,其Key为变量名称,Value为变量值。 另外一种方法是编译脚本期间就为这个变量分配好了索引值,所有变量都保存在一个一维数组里,这样的存取,相比于Map存取,有十倍以上性能提高。

如下一段脚本语言

var a = 1;
var b = 2+a;

有些语言引擎会翻译成类似如下java代码

context.put("a",1);
context.put("b",context.get("a")+2);

这里context是一个Map。从Map里通过Key存取尽管很快,但是不如使用数组,如上脚本采用数组,代码翻译成Java代码如下

Object[] vars = context.vars;
vars[0] =1 ;
vars[1] = vars[0]+2

这里为变量a,b 分别设置了在变量表中的索引是0和1;

另外一个例子是在可观测性工具中,记录方法的执行的耗费时间。考虑到一分钟此方法可能被调用数百万次,比如订单指标,因此记录此性能数据的最好的办法不是记录每次执行时间。而是以执行时间为Key,执行次数为value记录在一个Map中

//key是执行时长(毫秒),value是执行次数
Map<Integer,AtomicInteger> orderProfileData = ....

考虑到1分钟内,orderProfileData会被调用数十万次到百万次,要求此性能观测工具不能影响应用程序本身,因此可以使用数组代替Map,数组第一个元素代表执行1毫秒的次数,第二个元素代表执行2毫秒的次数,修改为如下代码:

static final int MAX  = 32;
//保存消耗时间为32毫秒的调用次数
static AtomicInteger[] counts = new AtomicInteger[MAX];
static Map<Integer, AtomicInteger> countMap = new ConcurrentHashMap<>();
static{
    for(int i=0;i<MAX;i++){
        counts[i] = new  AtomicInteger();
    }
}

假设被观测的方法通常执行不会超过32毫秒,此时用数组来保存执行时间和调用次数的关系,当执行时间超过32毫秒,再采用Map来存放。

其他采用数组代替Map的工具有如下

  • 高性能工具RelectASM(参考6.6反射优化,)也使用了类似技术提高访问对象属性的性能,把通过方法名调用改成通过方法编号调用。
  • Beetl 模版引擎也使用了类似的思想,通过变量名称访问变量值,改成通过为变量分配的索引值来快速访问变量。关于RelectASM和Beetl技术

知行合一