HashMap源码解析 一、HashMap数据结构介绍 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的key-value都存储在一个链表里.但是当位于一个桶中的元素较多即hash值相等的元素较多时,通过key值依次查找的效率较低.而在JDK1.8中,HashMap采用数组+链表+红黑树实现,但链表长度超过阈值8时,将链表转换为红黑树,这样大大减少了查找时间 下图代表JDK1.8之前的hashMap结构,左边部分即代表哈希表(也称为哈希数组),数组的每个元素都是单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中 JDK1.8之前的hashmap都采用上图的结构,都是基于一个数组和多个单链表,hash值冲突的时候,就将对应节点以链表的形式存储.如果在一个链表中查找其中一个节点时,将会花费O(n)的查找时间,会有很大的性能损失.到了JDK1.8,当同一个hash值的节点数不小于8时,不再采用单链表形式存储,而是采用红黑树,如下图所示
二、HashMap的类定义 1 public class HashMap <K ,V > extends AbstractMap <K ,V > implements Map <K ,V >, Cloneable , Serializable
可以看到HashMap继承自父类AbstractMap,实现了Map、Cloneable、Serializable接口 Map接口定义了一组通用操作 Cloneable接口则表示可以进行拷贝,在HashMap实现的是浅拷贝,即对拷贝对象的改变会影响被拷贝的对象 Serializable接口表示HashMap实现了序列化,即可以将HashMap对象保存至本地,之后可以恢复状态 HashMap是基于哈希表的Map接口的实现,此实现提供所有可选的映射操作,并且允许使用null键和null值.此类不保证映射的顺序特别是它不保证该顺序一直不变
1.HashMap的简单概括
HashMap中的元素按照hash值分布在不同的桶(bin)中.如果散列特性较好,那么元素分布会比较均匀
单个桶的尺寸过大时,桶的数据结构会由链式转换成一个红黑树,以保证对其操作的时间复杂度为O(logN)
key类型推荐实现Comparable,以提高效率
KeySet、Values、EntrySet三个视图集合只支持update/remove操作,不支持add
迭代子利用modCount属性来实现乐观锁,来如发生竞态的写操作,则迭代子将直接失效(fast-fail)
尽量直接调用系统函数:Node<K,V>中调用Object的hashCode和Equals
减少不必要的内部函数调用: resize中的transfer(Entry[] newTable)[逻辑优化]和indexFor(hash, table.length)
储存方式:位桶存储;若产生hash碰撞,位桶中按链表存储;若链表达到阀值,链表转换为红黑树
赋值巧妙:if ((p = tab[i = (n - 1) & hash]) == null)判断赋值两不误
通过bool或instance实现代码复用:putMapEntries(Map<? extends K, ? extends V> m, boolean evict)
非同步,若结构改变(add、delete,不包括修改value)必须在外部同步;考虑synchronizedMap–>Map m = Collections.synchronizedMap(new HashMap(…))
三、HashMap的属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; static final int MAXIMUM_CAPACITY = 1 << 30 ;static final float DEFAULT_LOAD_FACTOR = 0.75f ;static final int TREEIFY_THRESHOLD = 8 ;static final int UNTREEIFY_THRESHOLD = 6 ;static final int MIN_TREEIFY_CAPACITY = 64 ;feild transient Node<K,V>[] table;transient Set<Map.Entry<K,V>> entrySet;transient int size;transient int modCount;int threshold;final float loadFactor;
1.HashMap的变动条件 1.1table扩容
table为null或者table表的长度小于MIN_TREEIFY_CAPACITY(64)
当存储的实际大小超过临界值(容量*加载因子–默认的加载因子为0.75)时
1.2链表转红黑树 table表的长度大于MIN_TREEIFY_CAPACITY(64) 并且桶中的单链表长度超过TREEIFY_THRESHOLD(8) 时
1.3红黑树退化链表 桶中的红黑树长度小于UNTREEIFY_THRESHOLD(6) 时
四、HashMap的构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public HashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) { throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); } if (initialCapacity > MAXIMUM_CAPACITY) { initialCapacity = MAXIMUM_CAPACITY; } if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Illegal load factor: " + loadFactor); } this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); } public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; } public HashMap (Map<? extends K, ? extends V> m) { this .loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false ); } final void putMapEntries (Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0 ) { if (table == null ) { float ft = ((float )s / loadFactor) + 1.0F ; int t = ((ft < (float )MAXIMUM_CAPACITY) ? (int )ft : MAXIMUM_CAPACITY); if (t > threshold) { threshold = tableSizeFor(t); } } else if (s > threshold) { resize(); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false , evict); } } }
五、HashMap的核心函数分析 1.putVal 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 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 ) { treeifyBin(tab, hash); } break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { break ; } p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) { e.value = value; } afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) { resize(); } afterNodeInsertion(evict); return null ; }
注意:查找位置都分为桶中的第一个节点 、是否红黑树节点点 、链表由快到慢点 ,这样效率高,后面再进行对应位置插入、取值
put的逻辑:
判断键值对数组tab[]是否为空或为null,是则resize
根据键值key的hashCode()计算hash值得到当前Node的索引i,如果tab[i]==null(没碰撞),直接新建节点添加;如果碰撞转入3
判断当前数组中处理hash冲突方式为红黑树还是链表(check第一个节点类型即可),分别处理
是红黑树则按红黑树逻辑插入
是链表则遍历链表,看是否有key相同的节点
有则更新value值,没有则新建节点
此时若链表数量大于阈值8,则调用treeifBin方法(此方法先判断table是否为null或tab.length小于64,是则执行resize操作,否则才将链表改为红黑树)
如果size+1>threshold则resize
2.getNode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 ) { if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) { 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 ); } } return null ; }
(n-1) & hash计算bucket的index
判断第一个节点hash、key是否相等,是则返回first
若(e=first.next)!=null
若first为红黑树,getTreeNode返回根节点,并调用其find方法,根据hash值判断进入左/右子树,逐层查找
若为链表,遍历链表,得到节点值,通过hash和equals(key)确认所查找元素
没有改元素,返回null
get、containsKey都可以用来判断map中是否存在该元素吗? 当get()方法的返回值为null时,可能有两种情况:一种是在集合中没有该键对象,另一种是该键对象没有值本就为null.因此在Map集合中不应该利用get()方法来判断是否存在某个元素,而应该利用containsKey()方法来判断
3.resize 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; if (oldCap > 0 ) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) { newThr = oldThr << 1 ; } } else if (oldThr > 0 ) { newCap = oldThr; } else { newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int )(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0 ) { float ft = (float )newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float )MAXIMUM_CAPACITY ? (int )ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings ({"rawtypes" ,"unchecked" }) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null ) { for (int j = 0 ; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null ) { oldTab[j] = null ; if (e.next == null ) { newTab[e.hash & (newCap - 1 )] = e; } else if (e instanceof TreeNode) { ((TreeNode<K,V>)e).split(this , newTab, j, oldCap); } else { Node<K,V> loHead = null , loTail = null ; Node<K,V> hiHead = null , hiTail = null ; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0 ) { if (loTail == null ) { loHead = e; } else { loTail.next = e; } loTail = e; } else { if (hiTail == null ) { hiHead = e; } else { hiTail.next = e; } hiTail = e; } } while ((e = next) != null ); if (loTail != null ) { loTail.next = null ; newTab[j] = loHead; } if (hiTail != null ) { hiTail.next = null ; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
由前面知识可知,table的下标[bucket的index]由(n-1) & hash计算 n为table的length,hash即hash(key)计算方式如下
假设我们length从16resize到32(以下仅写出8位,实际32位),hash(key)是不变的
& | n-1(原长16)–>0000 1111 | 2n-1(新长32)–>1111 1111 | | :—: | :—: | :—: | hash1–>0000 0101 | 0000 0101 | 0000 0101 index1 | hash1高位全0,index不变 | hash1高位全0,index不变| hash2–>0001 0101 | 0000 0101 | 0001 0101 index2 | 新index=5+16即原index+oldCap | 新index=5+16即原index+oldCap |
新bucket下标:(2n-1) & hash(key),由于是&操作,同1为1;hash(key)是不变的;2n-1在原来n-1基础上仅高位由0变1
HashMap在resize时,只需看新n-1高位对应hash(key)位是0还是1即可,0则位置不变,1则位置变为原位置+oldCap
如何确认(新)n-1最高位对应的hash(key)位是0还是1呢?源码给出了很巧妙的方式(e.hash & oldCap): e即Node,由put和Node构造函数相关源码可知,e.hash即为hash(key); oldCap为0001 0000
总结:
无需重新计算hash,节省了时间
由于所计算的hash(key)位是1是0可以认为是随机的,所以将一个冲突长链表又”均分”成了两个链表,减少碰撞
说明:进行扩容会伴随着一次重新hash分配,并且会遍历hash表中所有元素,是非常耗时的.在编写程序时要尽量避免resize
在resize前和resize后的元素布局如下图: 上图只是针对了数组下标为2的桶中的各个元素在扩容后的分配布局,其他各个桶中的元素布局可以以此类推
3.1扩容思考 从putVal源代码中我们可以知道,当插入一个元素的时候size就加1,若size大于threshold的时候,就会进行扩容.假设我们的capacity大小为32,loadFator为0.75,则threshold为24 = 32 * 0.75,此时插入了25个元素,并且插入的这25个元素都在同一个桶中,桶中的数据结构为红黑树,则还有31个桶是空的,也会进行扩容处理,其实此时还有31个桶是空的,好像似乎不需要进行扩容处理,但是是需要扩容处理的,因为此时我们的capacity大小可能不适当.我们前面知道,扩容处理会遍历所有的元素,时间复杂度很高;前面我们还知道,经过一次扩容处理后,元素会更加均匀的分布在各个桶中,会提升访问效率.所以说尽量避免进行扩容处理,也就意味着,遍历元素所带来的坏处大于元素在桶中均匀分布所带来的好处
4.hash 1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
由上可知: key是有可能null的,并且会在0桶位的位置 hashCode的计算也进行了改进,取得key的hashcode后,高16位与低16位异或运算重新计算hash值 首先由key值通过hash(key)获取hash值h,再通过hash &(length-1)得到所在数组位置: 一般对于hash表的散列常用方法有直接定址法、除留余数法等,既要便于计算,又能减少冲突 在Hashtable中就是通过除留余数法散列分布的,具体如下:int index = (hash & 0x7FFFFFFF) % tab.length;但是取模中的除法运算效率很低,HashMap则通过hash &(length-1)替代取模,得到所在数组位置,这样效率会高很多
5.tableSizeFor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static final int tableSizeFor (int cap) { int n = cap - 1 ; n |= n >>> 1 ; n |= n >>> 2 ; n |= n >>> 4 ; n |= n >>> 8 ; n |= n >>> 16 ; return (n < 0 ) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 ; }
计算新的扩容临界值threshold,不再是JDK.16的while循环来保证hash表的容量一直是2的整数倍,用移位操作 取代了while循环移位(不断乘2) .HashMap的构造函数都直接或间接调用了tableSizeFor函数 1.6中
1 int capacity = 1 ; while (capacity < initialCapacity) capacity <<= 1
其返回值是>=cap的最小2的幂(大于等于1 ,tableSizeFor(-111)=1) 性能优化: length为2的整数幂 保证了length - 1 最后一位(二进制表示)为1,从而保证了索引位置index即(hash &length-1)的最后一位同时有为0和为1的可能性,保证了散列的均匀性.反过来讲若length为奇数,length-1最后一位为0,这样与h按位与[同1为1]的最后一位肯定为0,即索引位置肯定是偶数,这样数组的奇数位置全部没有放置元素,浪费了大量空间 简而言之:length为2的幂保证了按位与最后一位的有效性,使哈希表散列更均匀
六、HashMap的内部类 1.内部节点类Node 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static class Node <K ,V > implements Map .Entry <K ,V > { final int hash; final K key; V value; Node<K,V> next; public final K getKey () { return key; } public final V getValue () { return value; } public final String toString () { return key + "=" + value; } public final int hashCode () { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue (V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals (Object o) { if (o == this ) { return true ; } if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) { return true ; } } return false ; } }
2.TreeNode树节点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 static final class TreeNode <K ,V > extends LinkedHashMap .Entry <K ,V > { TreeNode<K,V> parent; TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; boolean red; final TreeNode<K,V> root () {}static <K,V> void moveRootToFront (Node<K,V>[] tab, TreeNode<K,V> root) {}final TreeNode<K,V> find (int h, Object k, Class<?> kc) {}static int tieBreakOrder (Object a, Object b) {}final void treeify (Node<K,V>[] tab) {}final Node<K,V> untreeify (HashMap<K,V> map) {}final TreeNode<K,V> putTreeVal (HashMap<K,V> map, Node<K,V>[] tab,int h, K k, V v) {}final void removeTreeNode (HashMap<K,V> map, Node<K,V>[] tab,boolean movable) {}final void split (HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {}static <K,V> TreeNode<K,V> rotateLeft (TreeNode<K,V> root,TreeNode<K,V> p) {}static <K,V> TreeNode<K,V> rotateRight (TreeNode<K,V> root,TreeNode<K,V> p) {}static <K,V> TreeNode<K,V> balanceInsertion (TreeNode<K,V> root,TreeNode<K,V> x) {}static <K,V> TreeNode<K,V> balanceDeletion (TreeNode<K,V> root,TreeNode<K,V> x) {}static <K,V> boolean checkInvariants (TreeNode<K,V> t) {}}
3.KeySet类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 final class KeySet extends AbstractSet <K > { public final int size () { return size; } public final void clear () { 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<>(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(); } } } }
4.Values 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 final class Values extends AbstractCollection <V > { public final int size () { return size; } public final void clear () { this .clear(); } public final Iterator<V> iterator () { return new ValueIterator(); } public final boolean contains (Object o) { return containsValue(o); } public final Spliterator<V> spliterator () { return new ValueSpliterator<>(this , 0 , -1 , 0 , 0 ); } public final void forEach (Consumer<? super V> 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.value); } } if (modCount != mc) { throw new ConcurrentModificationException(); } } } }
5.EntrySet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 final class EntrySet extends AbstractSet <Map .Entry <K ,V >> { @Override public final int size () { return size; } @Override public final void clear () { this .clear(); } @Override public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } @Override public final boolean contains (Object o) { if (!(o instanceof Map.Entry)) { return false ; } Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } @Override public final boolean remove (Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true , true ) != null ; } return false ; } @Override public final Spliterator<Map.Entry<K,V>> spliterator() { return new EntrySpliterator<>(this , 0 , -1 , 0 , 0 ); } @Override public final void forEach (Consumer<? super Map.Entry<K,V>> 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); } } if (modCount != mc) { throw new ConcurrentModificationException(); } } } }
6.KeyIterator、ValueIterator、EntryIterator 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 final class KeyIterator extends HashIterator implements Iterator <K > { @Override public final K next () { return nextNode().key; } } final class ValueIterator extends HashIterator implements Iterator <V > { @Override public final V next () { return nextNode().value; } } final class EntryIterator extends HashIterator implements Iterator <Map .Entry <K ,V >> { @Override public final Map.Entry<K,V> next () { return nextNode(); } }
如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put方法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key时modCount也会自增 2. jdk为什么允许通过iterator进行remove操作
HashMap和keySet的remove方法都可以通过传递key参数删除任意的元素,而iterator只能删除当前元素(current)[movable为false],一旦删除的元素是iterator对象中next所正在引用的,如果没有通过modCount、 expectedModCount的比较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了一个已移除的过期数据.ConcurrentModificationException是RuntimeException,不要在客户端捕获它.如果发生此异常,说明程序代码的编写有问题,应该仔细检查代码而不是在catch中忽略它 Iterator自身的remove()方法会自动同步expectedModCount和modCount的值(见上源码).确保遍历可靠的原则是只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步
7.KeySpliterator、ValueSpliterator、EntrySpliterator