我将尝试总结 cmets 中提出的三个不太合理的版本。
@Holgersays:
我猜这是为了避免类 java.util.Arrays 被加载作为此方法的副作用。对于应用程序代码,这通常不是问题。
这是最容易测试的。让我们编译这样的程序:
public class HashMapTest {
public static void main(String[] args) {
new java.util.HashMap();
}
}
使用java -verbose:class HashMapTest 运行它。这将在类加载事件发生时打印它们。使用 JDK 1.8.0_60,我看到加载了 400 多个类:
... 155 lines skipped ...
[Loaded java.util.Set from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.AbstractSet from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$EmptySet from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$EmptyList from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$EmptyMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$UnmodifiableCollection from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$UnmodifiableList from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.Collections$UnmodifiableRandomAccessList from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.Reflection from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
**[Loaded java.util.HashMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.HashMap$Node from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$3 from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$ReflectionData from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$Atomic from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.generics.repository.AbstractRepository from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.generics.repository.GenericDeclRepository from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.generics.repository.ClassRepository from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.Class$AnnotationData from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.annotation.AnnotationType from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.util.WeakHashMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.ClassValue$ClassValueMap from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.reflect.Modifier from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded sun.reflect.LangReflectAccess from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
[Loaded java.lang.reflect.ReflectAccess from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
**[Loaded java.util.Arrays from C:\Program Files\Java\jre1.8.0_60\lib\rt.jar]
...
如您所见,HashMap 早于应用程序代码加载,Arrays 仅在 HashMap 之后的 14 个类加载。 HashMap 加载由 sun.reflect.Reflection 初始化触发,因为它具有 HashMap 静态字段。 Arrays 负载很可能由WeakHashMap 负载触发,而clear() 方法中实际上有Arrays.fill。 WeakHashMap 负载由扩展 WeakHashMap 的 java.lang.ClassValue$ClassValueMap 触发。 ClassValueMap 存在于每个 java.lang.Class 实例中。所以在我看来,如果没有Arrays 类,JDK 根本无法初始化。 Arrays 静态初始化器也很短,它只初始化断言机制。此机制用于许多其他类(包括,例如,很早就加载的java.lang.Throwable)。 java.util.Arrays 中没有执行其他静态初始化步骤。因此@Holger 版本对我来说似乎不正确。
在这里我们还发现了非常有趣的事情。 WeakHashMap.clear() 仍然使用 Arrays.fill。当它出现在那里时很有趣,但不幸的是它转到了prehistoric times(它已经存在于第一个公共 OpenJDK 存储库中)。
接下来,@MarcoTopolnik says:
当然不会更安全,但是当fill 调用未内联且tab 很短时,它可能会更快。在 HotSpot 上,循环和显式 fill 调用都将导致快速编译器内在(在快乐的一天场景中)。
令我惊讶的是,Arrays.fill 并没有直接内在化(参见@apangin 生成的intrinsic list)。似乎这种循环可以被 JVM 识别和矢量化,而无需显式的内部处理。因此,在非常特殊的情况下(例如,如果达到MaxInlineLevel 限制),确实不能内联额外调用。另一方面,这是非常罕见的情况,它只是一次调用,它不是循环内的调用,它是静态的,不是虚拟/接口调用,因此性能提升可能只是微不足道的,并且仅在某些特定情况下。不是 JVM 开发人员通常关心的事情。
还应该注意的是,即使是 C1“客户端”编译器(第 1-3 层)也能够内联 Arrays.fill,例如在 WeakHashMap.clear() 中调用,因为内联日志 (-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining) 显示:
36 3 java.util.WeakHashMap::clear (50 bytes)
!m @ 4 java.lang.ref.ReferenceQueue::poll (28 bytes)
@ 17 java.lang.ref.ReferenceQueue::reallyPoll (66 bytes) callee is too large
@ 28 java.util.Arrays::fill (21 bytes)
!m @ 40 java.lang.ref.ReferenceQueue::poll (28 bytes)
@ 17 java.lang.ref.ReferenceQueue::reallyPoll (66 bytes) callee is too large
@ 1 java.util.AbstractMap::<init> (5 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 9 java.lang.ref.ReferenceQueue::<init> (27 bytes) inline (hot)
@ 1 java.lang.Object::<init> (1 bytes) inline (hot)
@ 10 java.lang.ref.ReferenceQueue$Lock::<init> (5 bytes) unloaded signature classes
@ 62 java.lang.Float::isNaN (12 bytes) inline (hot)
@ 112 java.util.WeakHashMap::newTable (8 bytes) inline (hot)
当然,它也很容易被智能且强大的 C2“服务器”编译器内联。因此,我认为这里没有问题。似乎@Marco 版本也不正确。
最后我们有几个来自@StuartMarks 的comments(他是JDK 开发人员,因此有一些官方声音):
有趣。我的直觉是这是一个错误。此变更集的审核线程是here,它引用了earlier thread,即continued here。早期线程中的初始消息指向 Doug Lea 的 CVS 存储库中的 HashMap.java 原型。我不知道这是从哪里来的。它似乎与 OpenJDK 历史中的任何内容都不匹配。
...无论如何,它可能是一些旧快照; for 循环在 clear() 方法中使用了很多年。 Arrays.fill() 调用是由this changeset 引入的,所以它在树中只存在了几个月。另请注意,this changeset 引入的基于 Integer.highestOneBit() 的二次幂计算也同时消失了,尽管在审查过程中注意到但忽略了这一点。嗯。
确实,HashMap.clear() 包含多年的循环,在 2013 年 4 月 10 日是 replaced 和 Arrays.fill,直到 9 月 4 日讨论的 commit 被引入时才停留不到半年。讨论的提交实际上是对 HashMap 内部的重大重写,以修复 JDK-8023463 问题。关于使用具有重复哈希码的键来毒化HashMap 的可能性很长,这会将HashMap 的搜索速度降低到线性,使其容易受到 DoS 攻击。解决这个问题的尝试是在 JDK-7 中执行的,包括一些 String hashCode 的随机化。因此,HashMap 实现似乎是从早期的提交中派生出来的,独立开发,然后合并到主分支中,覆盖了中间引入的几个更改。
我们可能会支持这个假设执行差异。取 version 删除 Arrays.fill (2013-09-04) 并将其与 previous version (2013-07-30) 进行比较。 diff -U0 输出有 4341 行。现在让我们在添加Arrays.fill (2013-04-01) 之前与version 进行比较。现在diff -U0 只包含 2680 行。因此,较新的版本实际上更类似于旧版本而不是直接父版本。
结论
因此,我同意 Stuart Marks 的结论。没有具体的理由删除Arrays.fill,只是因为中间的更改被错误地覆盖了。在 JDK 代码和用户应用程序中使用 Arrays.fill 非常好,例如,在 WeakHashMap 中使用。 Arrays 类在 JDK 初始化的早期就被加载了,具有非常简单的静态初始化器,并且 Arrays.fill 方法可以很容易地被客户端编译器内联,所以应该没有性能缺陷。