我可以重现你的结果,并解释它们。
再生产
public abstract class Benchmark {
final String name;
public Benchmark(String name) {
this.name = name;
}
abstract int run(int iterations) throws Throwable;
private BigDecimal time() {
try {
int nextI = 1;
int i;
long duration;
do {
i = nextI;
long start = System.nanoTime();
run(i);
duration = System.nanoTime() - start;
nextI = (i << 1) | 1;
} while (duration < 100000000 && nextI > 0);
return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return name + "\t" + time() + " ns";
}
public static void main(String[] args) throws Exception {
Benchmark[] benchmarks = {
new Benchmark("array lookup") {
Double[] array = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
@Override
int run(int iterations) throws Throwable {
double sum = 0;
for (int i = 0; i < iterations; i++) {
for (int j = 0; j < array.length; j++) {
sum += array[j];
}
}
return (int) sum;
}
}, new Benchmark("map lookup") {
Map<String, Double> map = new HashMap<>();
{
map.put("1", new Double(1));
map.put("2", new Double(2));
map.put("3", new Double(3));
map.put("4", new Double(4));
map.put("5", new Double(5));
map.put("6", new Double(6));
map.put("7", new Double(7));
map.put("8", new Double(8));
map.put("9", new Double(9));
map.put("10", new Double(10));
}
@Override int run(int iterations) throws Throwable {
String key = "1";
double sum = 0;
for (int i=0; i <iterations; i++){
for (int j =1; j<=map.size(); j++){
sum += map.get(key);
}
}
return (int) sum;
}
}
};
for (Benchmark bm : benchmarks) {
System.out.println(bm);
}
}
}
在我有点过时的笔记本上,而 JDK 1.7 是 -server 模式,这会打印:
array lookup 15.250 ns
map lookup 124.946 ns
你问,为什么我得到的结果与 durron597 不同?正如我在评论中指出的那样,他每次迭代都会打印到 System.out。如果这确实是 I/O,它比地图查找要昂贵得多。您可以通过将基准更改为:
}, new Benchmark("array lookup with printing") {
Double[] array = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
@Override
int run(int iterations) throws Throwable {
for (int i = 0; i < iterations; i++) {
double sum = 0;
for (int j = 0; j < array.length; j++) {
sum += array[j];
}
System.out.println(sum);
}
return 0;
}
}, new Benchmark("map lookup with printing") {
Map<String, Double> map = new HashMap<>();
{
map.put("1", new Double(1));
map.put("2", new Double(2));
map.put("3", new Double(3));
map.put("4", new Double(4));
map.put("5", new Double(5));
map.put("6", new Double(6));
map.put("7", new Double(7));
map.put("8", new Double(8));
map.put("9", new Double(9));
map.put("10", new Double(10));
}
@Override int run(int iterations) throws Throwable {
String key = "1";
for (int i=0; i <iterations; i++){
double sum = 0;
for (int j =1; j<=map.size(); j++){
sum += map.get(key);
}
System.out.println(sum);
}
return 0;
}
}
打印以下时间(如果 System.out 是一个文件,则 Eclipse 控制台的数字相似)
array lookup with printing 43301.251 ns
map lookup with printing 18330.935 ns
这大约是不打印的 100 倍,所以我们这里主要测量 I/O。
解释
数组查找只涉及检查数组索引是否有效、将索引添加到基地址以及从内存中读取一个字。在您的情况下,您甚至在数组上循环,这使 Java Hotspot VM 可以跳过边界检查。
HashMap 查找执行以下代码:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
// from String.class
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
...
}
即使考虑到很少使用某些分支,映射查找所包含的操作也远远超过数组访问。需要更长的时间也就不足为奇了。
当然,这种性能差异在实际代码中几乎不重要——而且比较是不公平的,因为 Map 比数组灵活得多。