在我的一个Java 6项目中,我有一个LinkedHashMap实例数组作为一个方法的输入,该方法必须迭代所有键(即通过所有映射的键集的并集)并使用相关值。并非所有映射中都存在所有键,并且该方法不应多次遍历每个键或更改输入映射。
我目前的实现如下:
Set<Object> keyset = new HashSet<Object>();
for (Map<Object, Object> map : input) {
for (Object key : map.keySet()) {
if (keyset.add(key)) {
...
}
}
}
HashSet实例确保不会多次执行任何操作。
不幸的是,这部分代码在性能方面非常关键,因为它经常被称为非常。事实上,根据分析器,超过10%的CPU时间花费在HashSet.add()
方法上。
我正在尽可能地优化这些代码。使用LinkedHashMap及其更有效的迭代器(与普通HashMap相比)是一个显着的提升,但我希望将基本上的簿记时间减少到最小。
事先将所有密钥放在HashSet中,使用addAll()
被证明效率较低,因为之后调用HashSet.contains()
的成本较高。
目前我正在研究是否可以使用位图(好吧,确切地说boolean[]
)来完全避免使用HashSet,但根据我的键范围,它可能根本不可能。
有更有效的方法吗?最好是不会对钥匙造成限制的东西吗?
编辑:
一些澄清和评论:
我确实需要所有来自地图的值 - 我不能删除它们中的任何一个。
我还需要知道每个值来自哪个地图。我的代码中缺少的部分(...
)将是这样的:
for (Map<Object, Object> m : input) {
Object v = m.get(key);
// Do something with v
}
一个简单的例子来了解我需要对地图做些什么,就像这样并行打印所有地图:
Key Map0 Map1 Map2
F 1 null 2
B 2 3 null
C null null 5
...
这不是我实际做的,但你应该明白这一点。
输入地图是极变量。实际上,此方法的每次调用都使用不同的一组。因此,我不会通过缓存他们的密钥联合来获得任何东西。
我的密钥都是String实例。它们使用单独的HashMap在堆上进行实例化,因为它们非常重复,因此它们的哈希代码已经被缓存并且大多数哈希验证(当HashMap实现在哈希代码之后检查两个键是否实际相等时)匹配)归结为身份比较(==
)。分析器确认只有0.5%的CPU时间花费在String.equals()
和String.hashCode()
上。
编辑2:
根据答案中的建议,我做了一些测试,分析和基准测试。最终我的性能提升了大约7%。我做了什么:
我将HashSet的初始容量设置为所有输入映射的集合大小的两倍。通过消除HashSet中的大多数(全部?)resize()
调用,这在1-2%的范围内获得了一些东西。
我使用Map.entrySet()
作为我正在迭代的地图。由于额外的代码以及担心额外的检查和Map.Entry
getter方法调用将超过任何优点,我最初避免使用这种方法。事实证明,整体代码稍快一些。
我相信有些人会开始尖叫我,但这里是:原始类型。更具体地说,我在上面的代码中使用了原始形式的HashSet。由于我已经使用Object
作为其内容类型,因此我不会失去任何类型的安全性。调用checkcast
时无用的HashSet.add()
操作的成本显然非常重要,足以在移除时将性能提高4%。为什么JVM坚持检查转换为Object
超出了我的范围......
答案 0 :(得分:2)
无法替代您的方法,但有一些建议(略微)优化现有代码。
keySet()
,因为它总是会在后台创建一个新集。使用entrySet()
,这应该快得多equals()
和hashCode()
的实施 - 如果它们“昂贵”,那么您会对add
方法产生负面影响。答案 1 :(得分:1)
如何避免使用HashSet取决于您正在做什么。
每次更改input
时,我只计算一次联合。对于查找次数,这应该是相对罕见的。
// on an update.
Map<Key, Value> union = new LinkedHashMap<Key, Value>();
for (Map<Key, Value> map : input)
union.putAll(map);
// on a lookup.
Value value = union.get(key);
// process each key once
for(Entry<Key, Value> entry: union) {
// do something.
}
答案 2 :(得分:0)
选项A是使用.values()方法并迭代它。但我想你已经想到了它。
如果经常调用代码,则可能值得创建其他结构(取决于数据更改的频率)。创建一个新的HashMap;任何一个哈希映射中的每个键都是这个键中的一个键,该列表使HashMaps保持在该键出现的位置。
如果数据有点静态(与查询频率有关),这将有所帮助,因此管理结构的过载相对较小,并且如果密钥空间不是很密集(密钥不会重复很多)不同的HashMaps),因为它会节省很多不需要的contains()。
当然,如果要混合数据结构,最好将所有数据结构封装在自己的数据结构中。
答案 3 :(得分:0)