Java 集合学习笔记:HashMap - 迭代器
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 集合学习笔记:HashMap - 迭代器相关的知识,希望对你有一定的参考价值。
Java 集合学习笔记:HashMap - 迭代器
iterators
HashIterator
自己实现了 hasNext
、nextNode
、remove
三个方法。(它没有实现 Iterator
)
因为是个抽象类
,无法实例化,所以只能作为其他类的公共部分
。
三个子类 KeyIterator
、ValueIterator
、EntryIterator
继承 HashIterator
并各自己实现自己的 next
。
共同实现了 Iterator
接口中的所有方法。
HashIterator
这段源码中亮点
应该就是通过 if
+ do while
实现遍历的部分了吧。
abstract class HashIterator
Node<K,V> next; // 下一次调用 nextNode 会返回的结点
Node<K,V> current; // 当前结点
int expectedModCount; // 预期修改次数。用于检测并发冲突
int index; // 当前索引
// 初始化迭代器
HashIterator()
expectedModCount = modCount; // 缓存修改次数
Node<K,V>[] t = table; // 缓存哈希表
current = next = null; // 当前、下一次将要返回的 entry 都置空
index = 0; // 当前所在位置的索引放到 0
// 如果哈希表不为空,先来到第一个结点
// do-while 遍历哈希表,找到第一个结点,给 next。(它也是所在桶的第一个节点)
if (t != null && size > 0) // advance to first entry
do while (index < t.length && (next = t[index++]) == null);
hasNext
public final boolean hasNext()
return next != null;
nextNode
final Node<K,V> nextNode()
Node<K,V>[] t;
Node<K,V> e = next;
// 检测并发冲突
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// 检测将要返回的结点如果为空就抛锅。因为调用 nextNode() 前,我们通常是检测过 hasNext()的。
if (e == null)
throw new NoSuchElementException();
// 1. 用即将返回的 e 更新 current 指针。
// 2. if 这句就是在遍历链表
// 2.1. 取出当前结点 current 的下一个结点给 next 指针。
// 2.2. 如果 next 为 null 说明链表遍历完了。要是哈希表不为空,就继续遍历哈希表,找下一个桶。
// 3. do-while 是遍历哈希表
// 3.1. index 还没走到头,就一直取下一个索引对应的值来判断。找到非空的,就是 next 指针该有的值了。
if ((next = (current = e).next) == null && (t = table) != null)
do while (index < t.length && (next = t[index++]) == null);
return e;
remove
// remove 与 nextNode 成对调用。移除后 current 会被置 null
public final void remove()
// 拿到当前对象,并检测。如果为空抛锅。
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
// 并发冲突检测
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// current 被置 null,在下次调用 nextNode 前都无法再使用 remove
current = null;
// 拿到 key 调用底层 removeNode 方法
K key = p.key;
removeNode(hash(key), key, null, false, false);
// 同步修改次数
expectedModCount = modCount;
KeyIterator
继承 HashIterator
重写 next
方法,返回具体的 key
值。
final class KeyIterator extends HashIterator implements Iterator<K>
public final K next() return nextNode().key;
ValueIterator
继承 HashIterator
重写 next
方法,返回具体的 value
值。
final class ValueIterator extends HashIterator implements Iterator<V>
public final V next() return nextNode().value;
EntryIterator
继承 HashIterator
重写 next
方法,原样返回结点 Node
,只不过返回类型改为接口 Map.Entry
。
Node
是 Map.Entry
的实现类:static class Node<K,V> implements Map.Entry<K,V> ...
final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>>
public final Map.Entry<K,V> next() return nextNode();
spliterators
- 可拆分迭代器的区间定义是
[左边界索引, 右边界索引)
右闭右开。 - 初始时如:
[0, arr.length]
HashMapSpliterator
作为其他几个可拆分迭代器的基类,实现了公用的方法。
static class HashMapSpliterator<K,V>
final HashMap<K,V> map; // 临时变量缓存将被拆分的 HashMap m
Node<K,V> current; // 当前结点(下一次将要处理的结点)
int index; // 左边界[包含] current 的索引, 在 advance/split 中被更改
int fence; // 右边界(不包含)
int est; // 元素个数(不是精确值,是个估值)
int expectedModCount; // 预期修改次数。用于检测并发冲突
// (map, 0, -1, 0, 0);
HashMapSpliterator(HashMap<K,V> m, int origin,
int fence, int est,
int expectedModCount)
this.map = m;
this.index = origin;
this.fence = fence; // 初始化 -1
this.est = est;
this.expectedModCount = expectedModCount;
getFence 获取拆分器的右边界
获取拆分器的右边界。(如果还没有就先获取)
- 将一堆临时变量初始化赋值。(只有第一次调用时才会进入 if )
- 算出并返回右边界。
final int getFence()
int hi;
// fence 初始化 -1,所以第一次调用此方法是会进入 if
if ((hi = fence) < 0)
HashMap<K,V> m = map; // 取hashmap实例
est = m.size; // 获取元素个数
expectedModCount = m.modCount; // 同步计数:预期修改次数 = 修改次数
Node<K,V>[] tab = m.table; // 缓存哈希表
hi = fence = (tab == null) ? 0 : tab.length; // 哈希表不为空返回长度。
return hi;
estimateSize 估计剩余元素的个数
public final long estimateSize()
getFence(); // 初始化
return (long) est;
KeySpliterator
static final class KeySpliterator<K,V>
extends HashMapSpliterator<K,V>
implements Spliterator<K>
/**
* @param m 将要被拆分的 HashMap
* @param origin 左边界
* @param fence 右边界
* @param est 剩余元素个数
* @param expectedModCount 预期修改次数
*/
KeySpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount)
super(m, origin, fence, est, expectedModCount);
1. trySplit 尝试拆分
尝试拆分,如果可拆分,创建一个新的拆分器
并返回。
注意:是按哈希表拆分,并不理会桶中结点的个数。
除非每个桶中都平均分配元素,否则 est >>>= 1
算出来的元素个数,肯定是不准确的。
- 算出左、中、右三个位置的索引。
- 判断如果还能拆分,就劈成两半,用左半边
[左,中)
创建一个新拆分器
返回。 - 当前
拆分器
更新一下左边界,新范围[中,右)
public KeySpliterator<K,V> trySplit()
// 获取右、左边界 hi 和 lo,算出中间位置 = (左 + 右) / 2
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
// 1. 左边界 >= 中间,表示已无可再分了。
// 2. 正常情况下 current 为空,原因见 tryAdvance 部分。
// 3. 新拆分器的范围[左,中),当前拆分器的左更新为中[中,右)
// new 的同时更新当前拆分器 index,est
// est >>>= 1 位运算结果作为参数。一步搞定当前和新拆分器的 size
return (lo >= mid || current != null) ? null
: new KeySpliterator<>(
map, lo, index = mid, est >>>= 1, expectedModCount
);
2. forEachRemaining 遍历剩余元素
遍历每个剩余元素依次执行给定的操作 action
,直到所有元素都处理完毕或该操作抛出异常。如果该Spliterator
为ORDERED
,则按遇到顺序执行操作。动作抛出的异常被传递给调用者。
- 准备工作:声明临时变量。检测并发冲突。初始化。
- 哈希表有内容就进去遍历。消费每一个元素的 key
public void forEachRemaining(Consumer<? super K> action)
// i 临时变量用于缓存当前桶索引
// hi 临时变量用于缓存右边界
// mc 临时变量用于缓存预期修改次数
int i, hi, mc;
// 传进来的 Lambda 为空,直接抛锅。是个 null 让我怎么执行?
if (action == null)
throw new NullPointerException();
HashMap<K,V> m = map; // 临时变量缓存将被拆分的 HashMap。下面会用它来取 table、modCount
Node<K,V>[] tab = m.table; // 临时变量缓存此 hashmap 中的哈希表
// 确保第一次调用时进入if。
// 道理与 getFence 中 if ((hi = fence) < 0) 一样。
// 对比一下就能发现,这里是个简化了的 getFence()
if ((hi = fence) < 0)
// 同步三个层级的:缓存预期修改次数
// 当前方法的 = 迭代器的 = hashMap的
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
else
mc = expectedModCount;
// 符合条件就执行遍历
// 1. 哈希表不为空,且长度 >= 右边界 (如果哈希表都没东西,就没必要遍历了)
// 2. 左边界 >= 0 (索引值不可能 < 0)
// 3. 当前索引 < 右边界 || 当前结点非空
// (没有右边界,可以继续找剩余的桶,当前结点非空,可以消费之)
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi) || current != null))
Node<K,V> p = current;
current = null;
// 真正开始遍历工作
do
// 如果结点 p 为null,说明链表走到头了,去下一个桶。
// 否则:先消费一下 key,再继续下一个结点。
// 继续条件:
// p != null 表示当前有结,可以继续。
// i < hi 表示还未到达右边界,可以继续。
if (p == null)
p = tab[i++];
else
action.accept(p.key);
p = p.next;
while (p != null || i < hi);
// 检测并发冲突
if (m.modCount != mc)
throw new ConcurrentModificationException();
3. tryAdvance 如果还有元素就消费一个
如果存在剩余元素,则对其执行给定操作 action
,返回true
;否则返回false
。
如果该Spliterator为ORDERED,则操作将按遇到顺序对下一个元素执行。
action
抛出的异常被传递给调用者。
注意:只有 current == null,的情况下,才可以才分。因为它不为空,说明调用过 n 次 tryAdvance current 走到了链表中的某个结点。此时不允许拆分,只有处理完当前桶中所有节点,current 为 null 后才能拆分。
- 准备工作:声明临时变量。检测并发冲突。初始化。
- 哈希表非空,且长度 >= 右边界,且当前索引 >=0 遍历结点
- 找到下一个结果,找到就消费它。
public boolean tryAdvance(Consumer<? super K> action)
int hi;
// 传进来的 Lambda 为空,直接抛锅。是个 null 让我怎么执行?
if (action == null)
throw new NullPointerException();
// 如果前面没报错,这里再声明临时变量拿哈希表。免得浪费资源。
Node<K,V>[] tab = map.table;
// 1. 哈希表不为空,且长度大于当前拆分器的右边界,且当前索引 >= 0
if (tab != null && tab.length >= (hi = getFence()) && index >= 0)
// current 不为空,则继续消费它。
// index < hi,表示当前还未走到右边界,可以继续。
while (current != null || index < hi)
// 如果 current 结点为 null,说明链表走到头了,去下一个桶看看。
// 否则,当前结点不为空继续遍历链表。
if (current == null)
current = tab[index++]; // 取下一个桶的首结点。(同时还更新了index)
else
K k = current.key; // 取出当前结点的 key
current = current.next; // 遍历到下一个结点。
action.accept(k); // 消费 key
// 检测并发冲突
if (map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true; // 消费成功返回 true
return false;
4. characteristics 返回特征
返回该Spliterator
及其元素的一组特征。
这里用的是位运算|
,8个特性每个特性占一位,可以组合。参考:《Java8实战》读书笔记06:Parallel Stream 并行流 - 表7-2 Spliterator的特性
在给定的分割器上重复调用characteristics()
,在调用trySplit
之前或中间,应该总是返回相同的结果。否则不能保证使用该Spliterator
进行的任何计算。
public int characteristics()
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) |
Spliterator.DISTINCT;
ValueSpliterator
逻辑与 KeySpliterator
基本相同,只是把消费对象从 key
改为了 value
。
static final class ValueSpliterator<K,V>
extends HashMapSpliterator<K,V>
implements Spliterator<V>
ValueSpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount)
super(m, origin, fence, est, expectedModCount);
public ValueSpliterator<K,V> trySplit()
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid || current != null) ? null :
new ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
expectedModCount);
public void forEachRemaining(Consumer<? super V> action)
int i, hi, mc;
if (action == null)
throw new NullPointerException();
HashMap<K,V> m = map;
Node<K,V>[] tab = m.table;
if ((hi = fence) < 0)
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
else
mc = expectedModCount;
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi以上是关于Java 集合学习笔记:HashMap - 迭代器的主要内容,如果未能解决你的问题,请参考以下文章