今天是祖国70大庆,没有出游而是选择了工作。因为这个里面涉及到一个接下来工作的核心,用Int替代String作为Map的key。我必须要充分的论证。
关于Hash的问题一直萦绕在我心头。在数据库计算,所有关乎Map的计算都会是一个耗时的计算,比如分组比如HashJoin等。因此可以肯定得说想加快计算速度,首先得搞定Map。所有Map中理论速度最快必然是Hash Map,get操作时间为产量时间。所以Hash Map必然是首选。
Hash Map分组查询
这里不深究其实现,只关注于其使用。
Equals
对于分组汇总来说,以String类型的作为Key是再正常不过了,例如:group by “省份”,”城市”。 也就是说这里的Key是”省份”,”城市”的一个组合Key。一般就会用一个String数组来存储省份和城市的值,如果select的是sum(“GDP”),那么可以简化用Map[String[],Int]这样结构类存储结果。我简单模拟了一下,结果如下
group size:59049
time:10074ms
hash:10000000
equals:9881672
分组数是6w,也就是有6w个key值。hash调用了1kw次,equals方法调用了1kw。这其实是非常糟糕的,因为equals方法调用太多了,说明有冲突。
下面我们将”省份”,”城市”替换为”省份ID”,”城市ID”,也就是group by “省份ID”,”城市ID”。再看一下最终结果
group size:59049
time:1724ms
hash:10000000
equals: 9941468
分组数与equals方法调用次数都比较相似,但是时间损耗却相差甚远。
再看一组数据,依然是用String和Int作为key。
String作为Key值的情况
group size:10000000
time:910ms
hash:10000000
equals: 11429
Int作为Key值的情况
group size:10000000
time:843ms
hash:10000000
equals: 11791
第一个例子String主要慢的原因是其equals速度远慢于int类型。因为存在大量的Hash冲突才导致这么多的equals调用。第二个列子中,我扩大了Key的范围,避免过多了的冲突,于是String的速度立刻降了下来。 为了更为直接的来比较int和string数组的equals差距,增加了一个针对equals的beachmark代码。
for (int i = 0; i < rowSize; i++) {
for (int j = 0; j < innerSize; j++) {
if (objectGroupKeys[index[i]].equals(objectGroupKeys[innerIndex[j]])) {
find++;
}
}
}
其中index和innerIndex都是为随机数,避免CPU缓存导致结果不准确。分别针对string和int得到以下数据
String的equals结果:
group size:48324
time:42174ms
find:27336
Int的equals结果:
group size:48243
time:13418ms
find:26732
时间上基本与分组计算中的比例相符,因此在相同的冲突比例下int数组要远优于String数组。
此外String还有个问题,String数组越长速度越慢,这和String的equals实现有关。下面的数据是在相同场景下,仅增加了String的key值长度。
String的equals结果:
group size:48280
time:145041ms
find:26812
Hash Code
通常情况下对map进行get或者put操作,key值的hashcode只会获取一次。如果key值是临时生成的,则没有必要缓存hash code值,但是如果key是反复利用的而且code对象比较复杂,计算相对耗时的话,将hashcode缓存下来是一个很好的优化。
冲突问题
如果Hash Map发生了验证的冲突,那么会是一件非常可怕的事情,Map的性能会急剧下降。来看一个数据,依然使用上面的场景中的代码,但是将int改为了Double对象。
group size:59049
time: 45143 ms
hash:10000000
equals: 1851012879
发现速度奇慢无比,根本原因是有大量的equals的调用,典型的hash发生了严重冲突。我将hash结果统计了一下,发现6w分组竟然只有1k不到的hash值。从equals次数上来看,可以看出平均一次get需要20次的equals
这里的冲突和Double对象的hashcode算法有关。通过简单的计算可以得到[0.0, 0.0]和[2.0,2.0]的hashcode是相同的,而模拟数据中的多数这样的double数据,于是发生了验证的冲突。
因此从上面可以看出,尽量避免使用String作为Key,优先使用Int。重写hashcode方法避免冲突。
##对付冲突 最理想的情况是没有冲突,用hashcode去取数总是唯一值。但是实际情况很难做到这样。首先空间不准许,其次hashcode是int类型,其长度也不准许。那么该如何避免激烈冲突呢.TO BE CONTINUE