一直搞不明白hashMap中entrySet()方法是如何将map转为Set的 由于好奇查看了一下源码结果一发不可收拾可能是我技术不精看的很模糊 最后偶然debugentrySet.toString 才大概明白
首先Map转Set的原理就是将 k : v 转为 “ k = v ” 保存的
看一下测试用例
@Test
public void test(){
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "张三A");
map.put(2, "李四");
map.put(3, "王五");
Set<Map.Entry<Integer, String>> entries = map.entrySet(); //这里用 Entry 是Map接口中的一个内部 接口,在这里实际上使用的是Node 类 采用了多态思想
System.out.println(entries。toString());
}
复制代码
现在我们来看看源码是怎么写的
1.在执行方法的时候会先查看有没有EntrySet类 如果有就使用 没有则新 new 一个 EntrySet
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
复制代码
那么EntrySet有何用处呢? EntrySet类代码比较多就不全放出来了 其主要实现了一些 操作方法 size() ,clear(),contains(Object o),remove(Object o),iterator()迭代器等 我们主要说iterator()
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
//注意 AbstractSet 继承于AbstractCollection<E>
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
//其迭代器继承关系
//final class EntryIterator extends HashIterator implements //Iterator<Map.Entry<K,V>> {
// public final K next() { return nextNode(); } 返回下个数据
}
}
复制代码
HashIterator 创建了一个快速失败迭代器 (Node是一个内部类用于保存map数据的 下面也有源码)
abstract class HashIterator {
Node<K,V> next; // 下一个返回的条目
Node<K,V> current; // 当前的条目
int expectedModCount; // 用于快速迭代中期待的正确值
int index; // 当前条数
HashIterator() {
expectedModCount = modCount; //modCount 是hashmap类中定义的一个用于保存修改次数的值
Node<K,V>[] t = table; //表数据
current = next = null; //赋初值
index = 0;
if (t != null && size > 0) { //赋初值
/*这行代码很有意思 先判断index有没有超过表中数据最大长度 重要的是后面的
表中的index(此时为0)也就是说在初始化的时候 next =t[0] current是没有数据的
还记得我们是怎么使用迭代器的吗?使用我们需要先 iterator.next()一下 然后再获取数据 想到这我 立马想到 该类必然有一个方法 把next的值赋值给current。在下面在nextNode()中我找到了答案*/
do {} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
//作用是判断在迭代期间有没有修改数据 以快速失败 如果修改了数据modCount就会自增 下面会列出put的源码
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
//(next = (current = e).next) 我们主要看这个
/** 1.把 e 赋值给了 current
2.将next在指向next的next
看这里就完成了 next赋值给 current 并且 在指向下一个节点的操作 不得不感慨 太强了啊!!!*/
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
removeNode(p.hash, p.key, null, false, false);
expectedModCount = modCount;
}
}
复制代码
附上Node类 的源码 实现 将 k : v 转为 “ k = v ” 的原理就在这
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
/*
这里注意 getKey() getValue() 是interface Entry<K, V> (Map的内部接口)。
*/
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
}
复制代码
附上 put的源码 modCount自增的行数有标记
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;/////////////////////////////////////////////////////////////
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
复制代码
好了到这部分我们就知道 是如何实现迭代的了 但是为什么 会将Map转成Set我们任然没有解决 我们继续,其实这个的原理真的很简单
System.out.println(entries.toString());
复制代码
对toString进行debug发现进入了AbstractCollection 类中 为什么会进入这个类呢?因为
EntrySet extends AbstractSet<Map.Entry<K,V>>
而AbstractSet又继承了AbstractCollection
AbstractCollection 重写了toString
public String toString() {
Iterator<E> it = iterator(); //这里new 的就是 EntrySet 下的 iterator()可以在回去看看
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e); //调用每个Node中的toString()
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
复制代码
之后我就看了一下
Set<Integer> set = map.keySet(); // 得到全部的key
Collection<String> value = map.values(); // 得到全部的value
复制代码
上面的keySet() values()实现方式基本和entrySet()的实现方式一样 嘿嘿 ! 一个小惊喜 这波买卖不亏
好了 上面的代码就比较简单不多说了 在这里还有一个小插曲 就是用完toString()想着直接打印一下entries 发现调用的println中的
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
复制代码
之后调用valueOf()
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
复制代码
又去调用该对象toString()方法 这也解开了我好久的疑惑 为什么不用toString 打印效果也和使用toString()一样了,这可是意外之喜呢!!!