有个疑问,hashmap是数组+链表的数据结构,那么它是怎么遍历的,为什么下面的代码遍历出来结果是有序的?是不是会有个keyset缓存着有序的key呢,答案是否定的
HashMap 遍历如下
HashMap aa = new HashMap<String,String>();
aa.put("c", "aaa");
aa.put("b", "bbb");
aa.put("a", "ccc");
Iterator it = aa.keySet().iterator();
while(it.hasNext()) {
String key = (String) it.next();
System.out.println("key "+key+" value "+aa.get(key));
}
Iterator it2 = aa.entrySet().iterator();
while(it.hasNext()) {
Map.Entry entry = (Entry) it.next();
System.out.println("key "+entry.getKey()+" value "+entry.getValue());
}
结果
key a value ccc
key b value bbb
key c value aaa
从头看下,new HashMap() 的时候并没有创建数据结构
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
添加数据的时候才会创建
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
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; >>> 首次put数据会调用resize初始化槽数组
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; >>>如果槽上第一个节点的key就是这个key的Entry
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); >>> 新建Node
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)))) >>>如果链上找到了这个key对应的Entry
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;
}
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) { >>> first设置为映射到的槽位上
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) >>>如果第一个key就equals,那么返回第一个
return first;
if ((e = first.next) != null) { >>>如果链表不为空
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null); >>>遍历链表查找key比较equals
}
}
return null;
}
那么遍历时候it = aa.keySet().iterator()会调用到KeyIterator
final class KeyIterator extends HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
它是继承自HashIterator
abstract class HashIterator {
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} while (index < t.length && (next = t[index++]) == null);>>>遍历指向第一个不为空的槽
}
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) { >>>next指向链表上的下一个
do {} while (index < t.length && (next = t[index++]) == null); >>>如果链表遍历完了,那么指向下一个不为空的槽
}
return e;
}
}
现在问题解决了,KeyIterator只是个遍历工具,每次都是从一个非空槽位遍历整个链表,然后遍历下一个非空槽位的链表。
而Key是按hash算出来的,以上算出来hash值 a b c 就有顺序了,所以出现了keyset有序的假象,但其实是散列的。
另外说下HashSet,HashSet内部完全的HashMap,属桥接模式,那它怎么做到保存的数据不重复的,其实很简单,主要代码map.put(e, PRESENT) 而PRESENT是Object,HashMap里的key可不就是不重复的。So easy。