欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

jdk欣赏-ArrayList(2)

程序员文章站 2022-07-14 12:06:08
...

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();
        }
最后再打断点调试一下,具体内部是如何工作的。调试内容就不再写了。

未完待续......




























相关标签: jdk