美文网首页
Java容器——ArrayList源码分析

Java容器——ArrayList源码分析

作者: 坠尘_ae94 | 来源:发表于2020-08-04 17:38 被阅读0次

Java常用集合类分析

ArrayList

ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了动态的增加和减少元素,实现了Collection和List接口,灵活的设置数组的大小等好处。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

属性

//    序列化
    private static final long serialVersionUID = 8683452581122892189L;
//    默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;
//    空的elementDate数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
//    默认的初始化空的elementDate数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//    ArrayList中存放元素的数组
    transient Object[] elementData; // non-private to simplify nested class access
//    ArrayList中包含的元素数
    private int size;

刚开始,定睛一看,ArrayList中有三个Object数组,一下不好分别哪个存放元素。于是用反射验证自己的猜想:

    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<>();
        strings.add("HI");
        strings.add("HE");

        reflectArrayList("elementData",strings);
    }

    public static void reflectArrayList(String param , ArrayList<String> strings){
        Class<? extends ArrayList> aClass = strings.getClass();
        try {
            Field elementData = aClass.getDeclaredField(param);
            elementData.setAccessible(true);
            for (Object o : (Object[]) elementData.get(strings)) {
                System.out.println(o);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

结果:

HI
HE
null
null
null
null
null
null
null
null

插入两个元素后,发现元素正存放在属性"elementData"中。

构造方法

ArrayList共有三个构造方法:

public ArrayList(){}
public ArrayList(int initialCapacity){}
public ArrayList(Collection<? extends E> c){}

无参构造方法将DEFAULTCAPACITY_EMPTY_ELEMENTDATA的值赋给elementData

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

int类型参数构造方法则是用于指定初始数组长度:

if (initialCapacity > 0) {
    this.elementData = new Object[initialCapacity];
}
..

难点来了,第三个构造方法接收一个Collection集合:

测试代码:

ArrayList<String> strings = new ArrayList<>(Arrays.asList("1","2","3"));

进入构造方法

elementData = c.toArray();
..

这是构造方法的第一行代码,看着好像很简单的样子,不就是把Collection集合变成数组的形式赋给elementData。

跟进:

        public Object[] toArray() {
            return Arrays.copyOf(a, a.length, Object[].class);
        }

这里为什么会进入Arrays的copyof方法?很简单,因为我图省事,上传构造方法传入的是Arrays.asList("1","2","3"),asList返回Arrays的内部类ArrayList,所以这里的调用Arrays.copyof。如果传入的是java.util.ArrayList,那么这里执行的应该是

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

调用了Arrays.copyof,似乎是复制集合

继续:

    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;
    }

进入方法后先判断,传入的参数是否是Object数组,按情况将copy变为Object数组或者Array类型的变量,之后再调用System.arraycopy

   public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

这是个本地方法,用于进行数组复制,我试过了,是数组,当我传入ArrayList,则报错:

java.lang.ArrayStoreException: arraycopy: destination type java.util.ArrayList is not an array

而且,它会进行引用拷贝

        ArrayCopyTest[] array = new ArrayCopyTest[]{new ArrayCopyTest(),new ArrayCopyTest()};
        Arrays.stream(array).forEach(System.out::print);
        System.out.println();
        //        需要已分配空间
        ArrayCopyTest[] res = new ArrayCopyTest[2];
        System.arraycopy(array, 0, res, 0, 2);
        Arrays.stream(res).forEach(System.out::print);

结果:

ArrayCopyTest@39ba5a14ArrayCopyTest@511baa65
ArrayCopyTest@39ba5a14ArrayCopyTest@511baa65

有意思的方法

add

加上重载方法一共由三个方法:

public void add(int index, E element) {}
public boolean add(E e) {}
private void add(E e, Object[] elementData, int s) {}

先看最简单的一个参数的重载:

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

这个modCount是ArrayList的父类AbstractList的一个属性:

protected transient int modCount = 0;

The number of times this list has been <i>structurally modified</i>. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.

代表ArrayList集合的修改次数。

重新调用add方法:

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

这就简单啦,先判断elementData的长度够不够,不够调用grow方法增加长度,不过grow方法也没看着那么简单,

private Object[] grow() {
        return grow(size + 1);
    }
//又调用重载方法
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

Arrays.copyof之前说过,这里就不说了,简而言之,数组复制。

看看newCapacity方法:

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //把原来容量的基础上加0.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

一套操作下就是扩容。

再看最后一个带有index和element的add方法:

public void add(int index, E element) {
    rangeCheckForAdd(index);
    modCount++;
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    elementData[index] = element;
    size = s + 1;
}

从参数就可以大致推断,这个方法可以将元素插入到指定位置上。

都和之前差不多,重点就在于传入System.arraycopy的参数,它让函数返回将index位置后的所有元素统一后移以为的数组。

iterator

一看就知道,用来迭代的

public Iterator<E> iterator() {
    return new Itr();
}

返回一个迭代器.

这个ArrayList的内部类Itr

        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such

主要通过这两个属性进行迭代.

比如它的next方法

        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];
        }

配合while以及hasNext方法使用,简直完美.

remove

加上重载方法一共俩.

public E remove(int index) {}//返回移除的元素
public boolean remove(Object o) {}

另外,居然看到了break-label

public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

这个found的作用是为了找到符合条件的最后一个元素,之后调用fastRemove进行移除.

    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

这个嘛,就是如果是最后一个元素要进行remove,则将其置为null,其他情况调用arraycopy

大家应该注意到了,modCount经常出现,但似乎没看见有啥用,要想有点了解,可以看看ArrayList中modCount的作用,这个变量可以帮助抛出并发异常,迭代器也可以凭此做到边循环边删除,可以康康【Java面试题】List如何一边遍历,一边删除?.

sort

二话不说,直接上源码:

    @Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        modCount++;
    }

可以看到,调用了Arrays.sort方法:

    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

进入TimeSort查看sort方法:

    static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

        /**
         * March over the array once, left to right, finding natural runs,
         * extending short natural runs to minRun elements, and merging runs
         * to maintain stack invariant.
         */
        TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen, c);
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            // Advance to find next run
            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

countRunAndMakeAscending函数是实现查找严格升序或者严格降序

private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
                                                Comparator<? super T> c) {
    assert lo < hi;
    int runHi = lo + 1;
    if (runHi == hi)
        return 1;

    // Find end of run, and reverse range if descending
    if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
        while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
            runHi++;
        reverseRange(a, lo, runHi);
    } else {                              // Ascending
        while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
            runHi++;
    }

    return runHi - lo;
}

最后返回的都是满足升序的个数.

这里最好看参考资料的两个关于sort的链接,我说的没他们好.

参考资料

相关文章

网友评论

      本文标题:Java容器——ArrayList源码分析

      本文链接:https://www.haomeiwen.com/subject/eawwrktx.html