为什么 Java HashMap 实现在调整大小时使用 transfer() 而不是 put()?

Why does the Java HashMap implementation use transfer() instead of put() when resizing?

在Java7HashMap实现中,在resize方法中,它调用了transfer which moves old elements to a new table. Why have they written a new method instead of calling put with all the old elements? The resize won't be triggered again due to threshold。调用 put 使代码更清晰。

/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable) {
    Entry[] src = table;
    int newCapacity = newTable.length;
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;
            do {
                Entry<K,V> next = e.next;
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}

一个重要的区别是 transfer 可以通过两种方式利用每个条目的 Entry 个对象已经存在的事实:

  • 它可以重用 Entry 对象本身以避免分配新对象(从而避免内存分配,从而减少 GC 压力)。
  • 它可以重用存储在 Entry 对象中的 hash 值,从而避免必须在映射中已有的每个键上调用 Object.hashValue(这在理论上可能是一项昂贵的操作).

基本上:如果 resize 仅根据 put 实施,则必须重新做很多可以轻松避免的工作。

JDK 的更高版本具有明显更复杂的 HashMap 实现,其中实现了更复杂的 transfer 方法(或等效方法)。

还值得指出的是,在 JDK 本身中完成时,即使以较少“简单”代码为代价的微小性能提升通常也是值得的:因为 HashMap 基本上用于每个Java 程序,以稍微降低可读性成本为代价尽可能地提高性能通常是值得的权衡。同样的推理同样适用于我们“凡人”编写的大多数其他软件。