iterator
接口介绍iterator
接口,也是集合你们庭中的一员。和其余的Map
和Collection
接口不一样,iterator
主要是为了方便遍历集合中的全部元素,用于迭代访问集合中的元素,至关于定义了遍历元素的规范,而另外的Map
和Collection
接口主要是定义了存储元素的规范。
还记得么?以前说的iterable
接口,有一个方法就是叫iterator()
,也是返回iterator
对象。java
迭代:不断访问集合中元素的方式,取元素以前先判断是否有元素,有则取出来,没有则结束,不断循环这个过程,直到遍历完里面全部的元素。 git
接口定义的方法以下: github
boolean hasNext(); // 是否有下一个元素 E next(); // 获取下一个元素 // 移除元素 default void remove() { throw new UnsupportedOperationException("remove"); } // 对剩下的全部元素进行处理,action则为处理的动做,意为要怎么处理 default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); }
可是值得注意的是,集合类的总体不是继承了iterator
接口,而是继承了iterable
接口,经过iterable
接口的方法返回iterator
的对象。值得注意的是,iterator
的remove()
方法,是迭代过程当中惟一安全的修改集合的方法,为什么这样说?
若是使用for循环索引的方式遍历,删除掉一个元素以后,集合的元素个数已经变化,很容易出错。例如redis
for(int i=0;i<collection.size();i++){ if(i==2){ collection.remove(i); } }
而iterator
的remove()
方法则不会出错,由于经过调用hasNext()
和next()
方法,对指针控制已经处理得比较完善。编程
首先,咱们知道iterator
接口是为了定义遍历集合的规范,也是一种抽象,把在不一样集合的遍历方式抽象出来,这样遍历的时候,就不须要知道不一样集合的内部结构。 数组
为何须要抽象?安全
假设没有iterator
接口,咱们知道,遍历的时候只能经过索引,好比数据结构
for(int i=0;i<array.size();i++){ T item = array[i]; }
这样一来,耦合程度比较高,若是使用的数据结构变了,就要换一种写法,不利于维护已有的代码。若是没有iterator
,那么客户端须要维护指针,至关于下放了权限,会形成必定程度的混乱。抽象则是把遍历功能抽取出来,交给iterator
处理,客户端处理集合的时候,交给更“专业”的它,it do it well.多线程
ListIterator
继承于Iterator
接口,功能更强大,只能用于访问各类List
类型,使用List
类型的对象list
,调用listIterator()
方法能够获取到一个指向list
开头的ListIterator
分布式
从上面图片接口看,这个接口具备访问下一个元素,判断是否有下一个元素,是否有前面一个元素,判断是否有前一个元素,获取下一个元素的索引,获取上一个元素的索引,移除元素,修改元素,增长元素等功能。和普通的Iterator
不同的是,ListIterator
的访问指针能够向前或者向后移动,也就是双向移动。
boolean hasNext(); //是否还有元素 E next(); //获取下一个元素 boolean hasPrevious(); //是否有上一个元素 E previous(); // 获取上一个元素 int nextIndex(); //获取下一个索引 int previousIndex(); //获取上一个索引 void remove(); //移除 void set(E e); //更新 void add(E e); //添加元素
测试代码以下:
List<String> list = new ArrayList<String>(Arrays.asList("Book","Pen","Desk")); // 把指针指向第一个元素 ListIterator<String> lit = list.listIterator(1); while(lit.hasNext()){ System.out.println(lit.next()); } System.out.println("==================================="); //指针指向最后一个元素列表中的最后一个元素修改ChangeDesk。 lit.set("ChangeDesk"); // 往前面遍历 while(lit.hasPrevious()){ System.out.println(lit.previous()); }
输出以下:
Pen Desk =================================== ChangeDesk Pen Book
若是点开ArrayList
的源码,看到与ListIterator
相关的部分,咱们会发现其实ArrayList
在底层实现了一个内部类ListItr
,继承了Itr
,实现了ListIterator
接口。这个Itr
其实就是实现了Iterator
,实现了基本的List迭代器功能,而这个ListItr
则是加强版的专门为List
实现的迭代器。里面使用cursor
做为当前的指针(索引),全部函数功能都是操做这个指针实现。
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { super(); // 设置当前指针 cursor = index; } public boolean hasPrevious() { // 不是第一个元素就代表有前一个元素 return cursor != 0; } // 获取下一个元素索引 public int nextIndex() { return cursor; } // 获取前面一个元素索引 public int previousIndex() { return cursor - 1; } @SuppressWarnings("unchecked") public E previous() { //检查是否被修改 checkForComodification(); int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i; // 返回前一个元素 return (E) elementData[lastRet = i]; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }
咱们能够看到,在上面方法中,有不少校验,好比checkForComodification()
,意为检查是否被修改,list中的元素修改有可能致使数组越界。
准确地来讲,SpitIterator
和Iterator
并无什么关系,只是两个功能上有相似。SpitIterator
主要是定义类将集合分割成多个集合,方便并行计算。
public interface Spliterator<T> { // 顺序处理每个元素,参数是处理的动做,若是还有元素须要处理则返回true,不然返回false boolean tryAdvance(Consumer<? super T> action); // 依次处理剩下的元素 default void forEachRemaining(Consumer<? super T> action) { do { } while (tryAdvance(action)); } // 最重要的方法,用来分割集合 Spliterator<T> trySplit(); //估算还有多少元素须要遍历处理 long estimateSize(); // 获取准确的元素,若是不能获取准确的,则会返回估算的 default long getExactSizeIfKnown() { return (characteristics() & SIZED) == 0 ? -1L : estimateSize(); } // 表示该Spliterator有哪些特性,这个像是个拓展功能,更好控制和优化Spliterator使用 int characteristics(); // 判断是否有哪些特性 default boolean hasCharacteristics(int characteristics) { return (characteristics() & characteristics) == characteristics; } // 若是这个Spliterator的源具备已排序的特征,那么这个方法将返回相应的比较器。若是源按天然顺序排序,则返回 // null。不然,若是源未排序,则抛出IllegalStateException。 default Comparator<? super T> getComparator() { throw new IllegalStateException(); } public static final int ORDERED = 0x00000010; public static final int DISTINCT = 0x00000001; public static final int SORTED = 0x00000004; public static final int SIZED = 0x00000040; public static final int NONNULL = 0x00000100; public static final int IMMUTABLE = 0x00000400; public static final int CONCURRENT = 0x00001000; public static final int SUBSIZED = 0x00004000; }
使用的方法例子以下:
public static void spliterator(){ List<String> list = Arrays.asList("1", "2", "3","4","5","6","7","8","9","10"); // 获取可迭代器 Spliterator<String> spliterator = list.spliterator(); // 一个一个遍历 System.out.println("tryAdvance: "); spliterator.tryAdvance(item->System.out.print(item+" ")); spliterator.tryAdvance(item->System.out.print(item+" ")); System.out.println("\n-------------------------------------------"); // 依次遍历剩下的 System.out.println("forEachRemaining: "); spliterator.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); // spliterator1:0~10 Spliterator<String> spliterator1 = list.spliterator(); // spliterator1:6~10 spliterator2:0~5 Spliterator<String> spliterator2 = spliterator1.trySplit(); // spliterator1:8~10 spliterator3:6~7 Spliterator<String> spliterator3 = spliterator1.trySplit(); System.out.println("spliterator1: "); spliterator1.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator2: "); spliterator2.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator3: "); spliterator3.forEachRemaining(item->System.out.print(item+" ")); }
结果以下:
tryAdvance: 1 2 ------------------------------------------- forEachRemaining: 3 4 5 6 7 8 9 10 ------------------------------------------ spliterator1: 8 9 10 ------------------------------------------ spliterator2: 1 2 3 4 5 ------------------------------------------ spliterator3: 6 7
还有一些其余的用法在这里就不列举了,主要是trySplit()以后,能够用于多线程遍历。理想的时候,能够平均分红两半,有利于并行计算,可是不是必定平分的。
spliterator
能够将其实现特征表示为同一接口中定义的一组常量。也就是咱们见到的ORDERED
,DISTINCT
,SORTED
,SIZED
之类的,这个意思是每个实现类,都有本身的实现方式,实现方式不一样,实现特征也不同,好比ArrayList
实现特征是ORDERED
,SIZED
和SUBSIZED
,这个咱们能够经过characteristics()
and hasCharacteristics()
来判断。例如:
public static void main(String[] args) throws Exception{ List<String> list = new ArrayList<>(); Spliterator<String> s = list.spliterator(); System.out.println(s.characteristics()); if(s.hasCharacteristics(Spliterator.ORDERED)){ System.out.println("ORDERED"); } if(s.hasCharacteristics(Spliterator.DISTINCT)){ System.out.println("DISTINCT"); } if(s.hasCharacteristics(Spliterator.SORTED)){ System.out.println("SORTED"); } if(s.hasCharacteristics(Spliterator.SIZED)){ System.out.println("SIZED"); } if(s.hasCharacteristics(Spliterator.CONCURRENT)){ System.out.println("CONCURRENT"); } if(s.hasCharacteristics(Spliterator.IMMUTABLE)){ System.out.println("IMMUTABLE"); } if(s.hasCharacteristics(Spliterator.NONNULL)){ System.out.println("NONNULL"); } if(s.hasCharacteristics(Spliterator.SUBSIZED)){ System.out.println("SUBSIZED"); } }
输出的结果是
16464 ORDERED SIZED SUBSIZED
输出结果中的16464和其余的怎么挂钩的呢?其实咱们发现上面的hasCharacteristics()
方法中,实现是return (characteristics() & characteristics) == characteristics;
,不难看出,这些状态是根据与运算来计算出来的。上面的结果也代表ArrayList
有ORDERED
,SIZED
和SUBSIZED
这几个特征。
若是是HashSet
则特征是DISTINCT
和SIZED
。
iterator
只是一个接口,至关于一个规范,全部的子类或者继承类实现的时候理论上应该遵照,可是不同的继承类/子类会有不同的实现。
iterator
只是一个接口,一个规范,虽然里面有个别方法有默认实现,可是最重要也最丰富的的,是它在子类中的实现与拓展,如今来看在ArrayList
中的实现。ArrayList
并无直接去实现iterator
接口,而是经过内部类的方式来操做,内部类为Itr
,
private class Itr implements Iterator<E> { // 下一个元素的索引(指针) int cursor; // index of next element to return // 最后一个元素指针索引 int lastRet = -1; // index of last element returned; -1 if no such // 修改次数(版本号) int expectedModCount = modCount; Itr() {} // 是否有下一个元素 public boolean hasNext() { return cursor != size; } // 下一个元素 @SuppressWarnings("unchecked") public E next() { //安全检查 checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } // 移除 public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } // 依次处理剩下的元素 @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } // 安全检查,检查是否被修改 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
从上面的源码能够看到,不少关于被修改的检查,集合会追踪修改(增删改)的次数(modCount 又称版本号),每个迭代器会单独立维护一个计数器,在每次操做(增删改),检查版本号是否发生改变,若是改变,就会抛出ConcurrentModificationException() 异常,这是一种安全保护机制。
安全检查,快速失败机制实现主要和变量modCount
,expectedModCount
,以及一个checkForComodification()
方法有关,也就是expectedModCount
是内部类的修改次数,从字面意思看是指理论上期待的修改次数,modCount
是外部类的修改次数,建立的时候,会将modCount
赋值给expectedModCount
,二者保持一致,若是在迭代的过程当中,外部类的modCount
对不上expectedModCount
,n那么就会抛出ConcurrentModificationException
异常。
首先,HashMap
里面定义了一个HashIterator
,为何这样作呢?由于HashMap
存储结构的特殊性,里面有Entry<key,value>,因此遍历就有三种状况,一个是Key,一个是Value,另外一个就是Entry,这三个的迭代遍历都有类似性,因此这里根据抽象原则,定义了一个Hash迭代器。
abstract class HashIterator { // 下一个节点 Node<K,V> next; // 当前节点 Node<K,V> current; // current entry // 指望修改次数 int expectedModCount; // for fast-fail // 索引 int index; // current slot HashIterator() { expectedModCount = modCount; Node<K,V>[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // 指向第一个不为空的元素 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; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); 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; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } }
以后分别定义KeyIterator
,ValueIterator
,EntryIterator
,继承于HashIterator
,
// 遍历key final class KeyIterator extends HashIterator implements Iterator<K> { public final K next() { return nextNode().key; } } // 遍历value final class ValueIterator extends HashIterator implements Iterator<V> { public final V next() { return nextNode().value; } } //遍历entry final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } }
以上的种种,关于Iterator
,其实就是一个迭代器,可简单地理解为遍历使用,主要功能是指向一个节点,向前或者向后移动,若是数据结构复杂就须要多个迭代器,好比HashMap
,能够避免多个迭代器之间相互影响。每个迭代器都会有
expectedModCount 和modCount,就是校验这个迭代过程当中是否被修改,若是修改了,则会抛出异常。
【做者简介】:
秦怀,公众号【秦怀杂货店】做者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。我的写做方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都彻底正确,可是我保证所写的均通过实践或者查找资料。遗漏或者错误之处,还望指正。
平日时间宝贵,只能使用晚上以及周末时间学习写做,关注我,咱们一块儿成长吧~