jdk欣赏-ArrayList(2)

xiaoxiao2021-02-28  8

ArrayList中有两个转换为数组的方法,Object[] toArray()和<T> T[] toArray(T[] a)。

两个方法的唯一区别就是返回的数据类型不同,最终都是这个方法:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }因为前一篇说过,实际ArrayList中保存数据的是一个Object类型的数组,所以默认ArrayList转换成的数组就是Object类型。

ArrayList中还有一个比较常用的内部类SubList,利用方法:

public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }获取一个当前ArrayList的类似于视图的 SubList对象,从[fromIndex,toIndex)这么一个左闭右开的区间范围,对于原ArrayList和这个SubList的“non-structural changes”(非结构性改变)会互相影响,这里非结构性的改变指不改变大小。

如果这个SubList发生了结构性的变化,那么原ArrayList也会相应发生改变;如果原ArrayList发生了结构性变化,从语义上来说,SubList变成undefined。

从代码实现角度上来说,针对SubList的操作,都会先进行以此判断,比较当前SubList的修改次数与原ArrayList修改次数及modCount是否相同。如果不相同则会抛出ConcurrentModificationException异常。并且在针对SubList操作之后,都会同步修改原ArrayList的modCount值。

private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }所以在使用SubList时我们需要谨慎一些,尽量在只需要针对原ArrayList的部分进行操作时,使用SubList,并且注意不要修改原ArrayList大小。

从jdk1.8开始,对于ArrayList的遍历多了一种方式,其内部实际也是for循环遍历,只不过采用了函数编程的思想和写法。

public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); final int expectedModCount = modCount; @SuppressWarnings("unchecked") final E[] elementData = (E[]) this.elementData; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { action.accept(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }

在jdk1.8还新增加一个新特性,ArrayListSpliterator实现了Spliterator这个接口,也是1.8新增加的特性。

 在1.8中stream内部实现都会接受一个Spliterator参数,

default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }

接口Spliterator有如下几个方法:

当还有需要处理的元素时,那么对该元素使用action处理,并且返回true,否则返回false。

boolean tryAdvance(Consumer<? super T> action);对于剩下的元素遍历处理,实际调用tryAdvance方法,直到没有元素需要处理了,即tryAdvance返回false: default void forEachRemaining(Consumer<? super T> action) { do { } while (tryAdvance(action)); }

分解当前的迭代器,并且返回分解后的结果

Spliterator<T> trySplit();

主要是影响并发的线程数

long estimateSize();

获取当前迭代器的特性,不同特性会对以上方法有不同的影响,其中特性会有很多:

int characteristics(); 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;可以看出来,需要按位或,就可以得到其多有的特性。

我们学习一下ArrayList中是如何使用的,首先是分割迭代器:

public ArrayListSpliterator<E> trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; return (lo >= mid) ? null : // divide range in half unless too small new ArrayListSpliterator<E>(list, lo, index = mid, expectedModCount); }首先getFence会获取到迭代器分割的最高位置,lo为当前遍历的位置,mid为中间值,如果当前迭代器只有一个元素了,不再对其进行分割,否则等分为两部分。我们可以做一个测试。

List<String> ls = Lists.newArrayList("1", "2", "3", "4", "5", "6"); Spliterator<String> spliterator = ls.spliterator(); Spliterator<String> a = spliterator.trySplit(); Spliterator<String> b = a.trySplit(); Spliterator<String> c = b.trySplit(); spliterator.forEachRemaining(s -> System.out.print(s+" "));//4 5 6 System.out.println("-------------------------"); a.forEachRemaining(s -> System.out.print(s+" "));//2 3 System.out.println("-------------------------"); b.forEachRemaining(s -> System.out.print(s+" "));//1 System.out.println("-------------------------"); c.forEachRemaining(s -> System.out.print(s+" "));//NullPointerException

可以看到,迭代器b已经只有一个元素了,如果再继续进行切分会返回null。

计算fence的方法,初始化时fence=-1,则设置为当前ArrayList的大小,以后每次设置为了构造迭代器时的参数,即mid大小:

private int getFence() { // initialize fence to size on first use int hi; // (a specialized variant appears in method forEach) ArrayList<E> lst; if ((hi = fence) < 0) { if ((lst = list) == null) hi = fence = 0; else { expectedModCount = lst.modCount; hi = fence = lst.size; } } return hi; }再看tryAdvance方法,就是先获取当前迭代器的最大位置,比较一下当前遍历的位置与fence大小,小于代表还有需要处理的元素,则对其执行action;否则代表没有需要处理的元素,返回false即可:

public boolean tryAdvance(Consumer<? super E> action) { if (action == null) throw new NullPointerException(); int hi = getFence(), i = index; if (i < hi) { index = i + 1; @SuppressWarnings("unchecked") E e = (E)list.elementData[i]; action.accept(e); if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; }最后是forEachRemaining方法,这里没有采用默认的循环调用tryAdvance方法,而是自己遍历内部元素,进行处理:

public void forEachRemaining(Consumer<? super E> action) { int i, hi, mc; // hoist accesses and checks from loop ArrayList<E> lst; Object[] a; if (action == null) throw new NullPointerException(); if ((lst = list) != null && (a = lst.elementData) != null) { if ((hi = fence) < 0) { mc = lst.modCount; hi = lst.size; } else mc = expectedModCount; if ((i = index) >= 0 && (index = hi) <= a.length) { for (; i < hi; ++i) { @SuppressWarnings("unchecked") E e = (E) a[i]; action.accept(e); } if (lst.modCount == mc) return; } } throw new ConcurrentModificationException(); }最后再打断点调试一下,具体内部是如何工作的。调试内容就不再写了。

未完待续......

转载请注明原文地址: https://www.6miu.com/read-2250175.html

最新回复(0)