LinkedList中LLSpliterator划分遍历
一、源码阅读-知晓这个类到底是什么?
1、当调用LinkedList中的方法spliterator()
此方法返回的是一个内部类对象,用以遍历链表中的元素。
@Override
public Spliterator<E> spliterator() {
return new LLSpliterator<E>(this, -1, 0);
}
2、源码结构分析
1) 属性
//最小遍历单元,就是指当前Spliterator划分遍历对象中至少应当遍历的大小
static final int BATCH_UNIT = 1 << 10; // batch array size increment
//最大遍历单元,就是指当前Spliterator划分遍历对象中最多只能遍历的大小
static final int MAX_BATCH = 1 << 25; // max batch array size;
//当前集合
final LinkedList<E> list; // null OK unless traversed
//由于链表中没有显式使用下标索引,因此使用current代表当前操作指针
Node<E> current; // current node; null until initialized
//est预估大小,
int est; // size estimate; -1 until first needed
//如果读过LinkedList源码的话,就应该知道这个值等于值集合的modCount,
//modCount是继承自AbstractList的属性
//用来防止并发导致集合发生改变,一旦改变就会抛出异常。
int expectedModCount; // initialized when est set
//已遍历的大小
int batch; // batch size for splits
2) 构造方法
/**
初始化,list集合,初始化预估大小为-1,预期大小为0
*/
LLSpliterator(LinkedList<E> list, int est, int expectedModCount) {
this.list = list;
this.est = est;
this.expectedModCount = expectedModCount;
}
3) 核心方法
- 方法1:
//LinkedList中的数据特征值就是基于链表,那么具有ORDERED, //list一定会指定大小size(),那么就具有SIZED //ArrayList可以调用trySplit()时,也可以直接确认子Spliterator的大小。 public int characteristics() { return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; } - 方法2:
//getFence()强制初始化fence的值,获得预估大小 public long estimateSize() { public long estimateSize() { return (long) getEst(); } } - 方法3:
final int getEst() { int s; // force initialization final LinkedList<E> lst; //如果est小于0,表示初始化 if ((s = est) < 0) { //如果集合为null,那么est也是0 if ((lst = list) == null) s = est = 0; else { //获得list集合的modCount expectedModCount = lst.modCount; //current节点直接等于LinkedList的属性first节点开始遍历 current = lst.first; s = est = lst.size; } } return s; } - 方法4:
//Consumer就是一个消费者 public boolean tryAdvance(Consumer<? super E> action) { Node<E> p; if (action == null) throw new NullPointerException(); //初始化est,每消费一次,est预估大小直接-- //p = current) != null表示为null就不需要消费了 if (getEst() > 0 && (p = current) != null) { --est; //消费的是Node节点的Object属性对象 E e = p.item; //消费完毕后,current节点直接等于next节点 current = p.next; action.accept(e); if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; } - 方法5:
//Consumer就是一个消费者 public void forEachRemaining(Consumer<? super E> action) { Node<E> p; int n; if (action == null) throw new NullPointerException(); //初始化est //p = current) != null表示为null就不需要消费了 if ((n = getEst()) > 0 && (p = current) != null) { //消费完毕,current直接重置 current = null; //消费完毕,est剩余大小直接为0 est = 0; do { E e = p.item; p = p.next; action.accept(e); } while (p != null && --n > 0); } if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); } - 方法6:
//子划分遍历 public Spliterator<E> trySplit() { Node<E> p; //初始化est int s = getEst(); // if (s > 1 && (p = current) != null) { //第一次遍历batch=0,BATCH_UNIT=1024,之后再划分当前Spliterator的话,batch=上一次子划分出去的遍历大小 int n = batch + BATCH_UNIT; //如果n超出了集合大小,则取集合大小 if (n > s) n = s; //超出上限,则取上限 1<<30 if (n > MAX_BATCH) n = MAX_BATCH; //这里的重点是将链表转为数组来遍历。 Object[] a = new Object[n]; int j = 0; do { a[j++] = p.item; } while ((p = p.next) != null && j < n); //取完子遍历的数据,当前节点就等于子遍历最后一个节点的下一个节点 current = p; //batch等于子遍历大小 batch = j; //剩余预估大小等于当前this的总数量-子遍历划分出去的数量 est = s - j; return //返回由Spliterators工具中的静态方法,这里数据特征是ORDERED有序性 //本质是一个Spliterators.ArraySpliterator对象,其内部维护的是Object[] array; Spliterators.spliterator(a, 0, j, Spliterator.ORDERED); } return null; }

4) 使用技巧
均衡划分,每个子Spliterator均划分同样的大小。
A,A->B, A->C ,C->D。
本文详细解析了LinkedList中的LLSpliterator类,探讨了其如何实现链表元素的遍历与划分。从源码角度介绍了LLSpliterator的属性、构造方法及核心方法,如characteristics、estimateSize、tryAdvance等,并分享了均衡划分的使用技巧。
298

被折叠的 条评论
为什么被折叠?



