HashMap 中的 keySet() 方法可能更简洁

Fre*_*ddy 6 java hashmap keyset java-8

Mac OS 上的 JDK 8,查看 HashMap.java 中的以下代码:

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }
Run Code Online (Sandbox Code Playgroud)

对返回的 ks 的任何更改都将反映在 keySet 中,因为它们始终指向相同的底层集合,如果这是真的,可以写成:

    public Set<K> keySet() {
        if (keySet == null) {
            keySet = new KeySet();
        }
        return keySet;
    }
Run Code Online (Sandbox Code Playgroud)

两个代码片段的行为是否相同?

如果是这样,为什么HashMap使用第一个变体而不是第二个变体?

Fra*_*ani 3

缓存到局部变量是为了提高性能。生成的字节码较小,该字段被读取一次,因此缓存未命中可能只发生一次,以及其他一些事情。

这是一个相当高级的优化,应该只在非常频繁运行的代码片段上进行。之所以在这里应用它,可能是因为它HashMap是用 Java 1.2 编写的,当时 JIT 非常基础,因此类似的事情产生了相当大的影响。

这样的话,也是为了支持多线程访问而做的。 HashMap不同步,但如果以后不修改,可以通过安全发布共享。如果两个线程同时执行该方法,则可能会发生竞争条件:第一个读入if(keySet == null)可以读取另一个线程写入的较新值,而第二个读入可以return keySet;读取较旧的 ( null) 值。使用局部变量可确保在非空时使用相同的引用ifreturn所以它永远不可能回来null

  • 这不是真正的同步;而是真正的同步。读取仍然很活跃,因此两个或多个线程可能会创建自己的“KeySet”实例,而不是使用单个实例。此外,如果多个线程使用通过数据竞争发布的单个“KeySet”实例,那么它是安全的,因为“KeySet”是“HashMap”实际数据的不可变无状态视图。 (3认同)