HashMap 中的 keySet() 方法可能更简洁
keySet() method in HashMap could be terser
JDK 8 on mac OS,查看来自 HashMap.java 的以下代码:
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
对返回的 ks 的任何更改都将反映在 keySet 中,因为它们始终指向相同的基础集,如果这是真的,是否可以写成:
public Set<K> keySet() {
if (keySet == null) {
keySet = new KeySet();
}
return keySet;
}
这两个代码片段的行为是否相同?
如果是这样,为什么 HashMap
使用第一个变体而不是第二个变体?
缓存到局部变量是为了提高性能。生成的字节码更小,字段被读取一次,因此缓存未命中可能只发生一次,还有一些其他事情。
这是一个相当高级的优化,应该只在非常频繁的 运行 段代码上进行。之所以在这里应用它,可能是因为 HashMap
是在 Java 1.2 中编写的,当时 JIT 非常基础,因此这些东西产生了相当大的影响。
这样的话,也是为了支持多线程访问而做的。
HashMap
未同步,但如果以后未修改,可以通过安全发布共享。如果两个线程同时执行该方法,则可能会出现竞争条件:第一个读取 if(keySet == null)
可以读取另一个线程写入的较新值,第二个读取 return keySet;
读取较旧的值(null
) 价值。使用局部变量可确保 if
和 return
在非空时使用相同的引用。所以它永远不能 return null
.
@Fransesco 指出,局部变量仅作为优化保存。在少数情况下,它也避免了新对象的创建。
该实现不会在内部存储任何状态,并且会根据 java 文档
对所有操作的底层哈希图进行操作,并且对集合的更改是预期的
- 不允许添加和添加(抛出 UnsupportedOperationException)
- remove,retainAll预计修改底层map
供参考
/**
* Returns a {@link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation), the results of
* the iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a set view of the keys contained in this map
*/
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
据我所知,所有平台的行为都是相同的,而不仅仅是 Mac
JDK 8 on mac OS,查看来自 HashMap.java 的以下代码:
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
对返回的 ks 的任何更改都将反映在 keySet 中,因为它们始终指向相同的基础集,如果这是真的,是否可以写成:
public Set<K> keySet() {
if (keySet == null) {
keySet = new KeySet();
}
return keySet;
}
这两个代码片段的行为是否相同?
如果是这样,为什么 HashMap
使用第一个变体而不是第二个变体?
缓存到局部变量是为了提高性能。生成的字节码更小,字段被读取一次,因此缓存未命中可能只发生一次,还有一些其他事情。
这是一个相当高级的优化,应该只在非常频繁的 运行 段代码上进行。之所以在这里应用它,可能是因为 HashMap
是在 Java 1.2 中编写的,当时 JIT 非常基础,因此这些东西产生了相当大的影响。
这样的话,也是为了支持多线程访问而做的。
HashMap
未同步,但如果以后未修改,可以通过安全发布共享。如果两个线程同时执行该方法,则可能会出现竞争条件:第一个读取 if(keySet == null)
可以读取另一个线程写入的较新值,第二个读取 return keySet;
读取较旧的值(null
) 价值。使用局部变量可确保 if
和 return
在非空时使用相同的引用。所以它永远不能 return null
.
@Fransesco 指出,局部变量仅作为优化保存。在少数情况下,它也避免了新对象的创建。
该实现不会在内部存储任何状态,并且会根据 java 文档
对所有操作的底层哈希图进行操作,并且对集合的更改是预期的- 不允许添加和添加(抛出 UnsupportedOperationException)
- remove,retainAll预计修改底层map
供参考
/**
* Returns a {@link Set} view of the keys contained in this map.
* The set is backed by the map, so changes to the map are
* reflected in the set, and vice-versa. If the map is modified
* while an iteration over the set is in progress (except through
* the iterator's own <tt>remove</tt> operation), the results of
* the iteration are undefined. The set supports element removal,
* which removes the corresponding mapping from the map, via the
* <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
* <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt>
* operations. It does not support the <tt>add</tt> or <tt>addAll</tt>
* operations.
*
* @return a set view of the keys contained in this map
*/
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
据我所知,所有平台的行为都是相同的,而不仅仅是 Mac