【问题标题】:why HashMap.keySet() doesn't return an empty Set [closed]为什么 HashMap.keySet() 不返回空集 [关闭]
【发布时间】:2015-10-26 12:53:12
【问题描述】:

keySet 中的密钥从何而来? KeySet 类是 HashMap 的内部类,它可以访问 HashMap 变量,但没有像 Set<K> 这样的直接变量,它只存储要引用的映射键。

我只能找到Entry<K,V>[] 表。但它已经存储了键和值。
当调用new KeySet() 进行引用时,keySet() 方法会做些什么吗?可能是这样的:

for(Entry e : table) {
    keySet.put(e.getKey());
}

那么keySet存储的keys,在增删key-value的时候,也增删keySet中的keys一样吗?

public Set<K> keySet() {
    Set<K> ks = keySet;
    return (ks != null ? ks : (keySet = new KeySet()));
}

源代码只显示了一个new KeySet(),但是为什么不是空的,但是有key?为了更清楚:

Map map = new HashMap();
map.put(1, 1);  //null
map.keySet();   //[1]
map.put(2, 2);  //[1,2]
map.remove(2);  //[1]

在每一行调试和断点,检查每一行,观察map的keySet变量会显示上面的结果,对吧?

一旦keySet()被调用,put和remove对keySet的效果是一样的,对吧?我看过HashMap的put和remove方法。

对于“put()”,如果调用 addEntry -> createEntry -> 在调用“table[bucketIndex] = new Entry(hash, key, value, e);”之后keySet 将添加密钥,

for "remove()" ->removeEntryForKey -> 调用 table[i] = next; keySet中的key被删除了,所以我认为table[]和keySet之间一定有某种关联,然后我问了这个问题……

【问题讨论】:

  • “我希望我已经清楚地说明了我的问题”——恐怕一点也不。不清楚您是否已经看过代码并且不了解它是如何工作的,或者您是否正在尝试猜测代码的外观。
  • 你看过JDK中的代码了吗?
  • @JonSkeet 是的,但是为什么“keySet = new KeySet()”不返回一个空集,但一个集包含映射键

标签: java hashmap keyset


【解决方案1】:

keySet() 返回由HashMap 支持的内部Set 实现。因此,例如,在该 Set 上调用 contains(key) 会在支持 HashMap 上调用 containsKey(key)

它不会创建一个独立的集合来保存原始 HashMap 的键(正如您在代码 sn-p 中所建议的那样),因为这样的 Set 不会得到原始 HashMap 的支持,因此HashMap 中的更改不会反映在Set 中,反之亦然。

这是 Java 6 的实现:

/**
 * Each of these fields are initialized to contain an instance of the
 * appropriate view the first time this view is requested.  The views are
 * stateless, so there's no reason to create more than one of each.
 */
transient volatile Set<K>        keySet = null;

public Set<K> keySet() {
    Set<K> ks = keySet;
    return (ks != null ? ks : (keySet = new KeySet()));
}

private final class KeySet extends AbstractSet<K> {
    public Iterator<K> iterator() {
        return newKeyIterator();
    }
    public int size() {
        return size;
    }
    public boolean contains(Object o) {
        return containsKey(o);
    }
    public boolean remove(Object o) {
        return HashMap.this.removeEntryForKey(o) != null;
    }
    public void clear() {
        HashMap.this.clear();
    }
}

【讨论】:

  • 是的,我看过源代码,但我不明白密钥从哪里来,"return (ks != null ? ks : (keySet = new KeySet()));",i认为这只是返回一个新的 KeySet 空,但事实并非如此,它存储了映射键。
  • @Just.Joke 密钥未存储在 KeySet 实例中。 KeySet 的方法使用包含 HashMap 的实例来访问密钥。
  • 你的意思是它是在我们看不到的地方处理的?我知道内部类“KeySet”可以保存 HashMap 实例,而 keySet 只是直接引用键,但我找不到在哪里键,我刚发现一个是HashMap中的Entry[]表,不能直接引用,因为它不仅保存键还保存值,另一个是AbstractMap中的Set keySet,但是在new HashMap之后为null,所以我找不到 keySet 可以直接引用的 Set 变量。然后我想是否有一些新的 KeySet() 调用的方法可以从 Entry[] 表中取出键,但是没有要么....
【解决方案2】:

您可以浏览java.util.HashMap 的源代码以了解其工作原理。

keySet() 函数实际上返回了HashMap 实例的成员变量,如下 JDK 源代码:

 public Set<K>  [More ...] keySet() {
     Set<K> ks = keySet;
     return (ks != null ? ks : (keySet = new KeySet()));
 }

那么keySetHashMap 的成员变量,其中它是一个本地定义的类:

 private final class  [More ...] KeySet extends AbstractSet<K> {
     public Iterator<K>  [More ...] iterator() {
         return newKeyIterator();
     }

     public int  [More ...] size() {
         return size;
     }

     public boolean  [More ...] contains(Object o) {
         return containsKey(o);
     }

     public boolean  [More ...] remove(Object o) {
         return HashMap.this.removeEntryForKey(o) != null;
     }

     public void  [More ...] clear() {
         HashMap.this.clear();
     }

 }

如您所见,它只是为HashMap 中保存的相同数据定义了另一个“视图”。没有任何重复,因此 keySet 视图和原始地图视图之间的一致性得到了保证。

【讨论】:

  • 我看过源代码,但是为什么“keySet = new KeySet()”可以返回一个包含映射键的Set而不是一个空的Set?
  • keySet = new KeySet() 是一个语句,该语句的返回值是来自= 操作数右侧的值。这就是为什么你可以写像a = b = c 这样的东西,它可以工作;因为a = b = c 本质上是a = (b = c),其中(b = c) 返回c 的值(对于java,如果c 是对象,则为指向c 的指针,如果是原始对象,则为c 的值);然后a就可以成功赋值给c
  • sorry,我想表达一下为什么“new KeySet()”不为空?当map不为空时,调用keySet()方法,它是new一个KeySet,但是keySet是newed不是空的,它已经存储了这个映射有的键。为什么只在“new KeySet()”之后它不是空的
  • 如您所见,new KeySet() 实际上是指包装地图的元素。它只是一个包装Set 方法的结构,以使代码更有条理。他们甚至可以让地图实现Set 接口,并在他们决定以这种方式组织代码时返回它(当然它并不干净)。
  • 嗯,我认为我的问题声明不够清楚,我的英语太糟糕了......我会更新问题并尝试更清楚地声明
【解决方案3】:

好的,我明白了。该方法确实返回一个空的 KeySet。但是当它中断时。eclipse 将调用方法 AbstractCollection.toString()...然后调用 KeySet.iterator() 方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-17
    • 2018-08-15
    相关资源
    最近更新 更多