ArrayList源码阅读笔记

来源:互联网 发布:淘宝网的盈利模式答案 编辑:程序博客网 时间:2024/05/09 12:48

初始化

/**

     * Default initial capacity.一个默认的初始化容量 10

     */

    private static final int DEFAULT_CAPACITY = 10;

 

/**

     * The size of the ArrayList (the number of elements it contains).

     * 存的是元素的个数

     * @serial

     */

    private int size;

/**  初始化

     * Constructs an empty list with the specified initial capacity.

     *

     * @param  initialCapacity  the initial capacity of the list

     * @throws IllegalArgumentException if the specified initial capacity

     *         is negative初始值为负数报此异常

     */

    public ArrayList(int initialCapacity) {

        if (initialCapacity > 0) {

            this.elementData = new Object[initialCapacity];

        } else if (initialCapacity == 0) {

            this.elementData = EMPTY_ELEMENTDATA;

        } else {

            throw new IllegalArgumentException("Illegal Capacity: "+

                                               initialCapacity);

        }

    }

注意 collection.toArray()

注意 list1的类是ArrayList,不是ArrayList

注意 数组getClass和普通类getClass的区别:

 

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

        elementData = c.toArray();

        if ((size = elementData.length) != 0) {

            // c.toArray might (incorrectly) not return Object[] (see 6260652)

            if (elementData.getClass() != Object[].class)

                elementData = Arrays.copyOf(elementData, size, Object[].class);

        } else {

            // replace with empty array.

            this.elementData = EMPTY_ELEMENTDATA;

        }

    }

 

增加--add(E e)

public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        elementData[size++] = e;

        return true;

}

普通插入,调用ensureCapacityInternal保证有size++的位置以储存e

private void ensureCapacityInternal(int minCapacity) {

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

        }

 

        ensureExplicitCapacity(minCapacity);

    }

如果elementDate是用(0)初始化建立的,那么把minCapacityDEFAULT_CAPACITY中的最大值当做ensureExplicitCapacity的参数保证element数组有空间

private void ensureExplicitCapacity(int minCapacity) {

        modCount++;

        // overflow-conscious code

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

如果element数组长度小于期望的容量最小值,那么就grow(minCapacity)增长到期望的最小值。

private void grow(int minCapacity) {

        // overflow-conscious code

        int oldCapacity = elementData.length;

        int newCapacity = oldCapacity + (oldCapacity >> 1);

        if (newCapacity - minCapacity < 0)

            newCapacity = minCapacity;

        if (newCapacity - MAX_ARRAY_SIZE > 0)

            newCapacity = hugeCapacity(minCapacity);

        // minCapacity is usually close to size, so this is a win:

        elementData = Arrays.copyOf(elementData, newCapacity);

    }

PsoldCapacity>>1相当于除以2

采用的策略是用 当前长度 + 当前长度/2 = newCapacity,如果newCapacity还是达不到期望的最小值,那么就newCapacity=minCapacity,导致newCapacity一定是大于等于期望的最小容量的,为原先的1.5倍。

如果newCapacity 大于规定的数组的最大值

PSMAX_ARRAY_SIZE = Integer.MAX_VALUE - 8

那么就比较minCapacity与数组最大值的大小,如果大于,就让newCapacity = Integer.MAX_VALUE,反之则=MAX_ARRAY_SIZE

最后调用Arrays.copyOf(elementData, newCapacity);加长数组。

PSArrays.copyOf(...)的源码:

public static <T> T[] copyOf(T[] original, int newLength) {

        return (T[]) copyOf(original, newLength, original.getClass());

    }

看得出这是一个用于增大数组长度的方法。

该扩容方法可能会导致有的空间没有存储元素。

 

增加--add(int index,E element)

public void add(int index, E element) {

        rangeCheckForAdd(index);

 

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index + 1,

                         size - index);

        elementData[index] = element;

        size++;

}

按照index插入

先检查是否出界,如果出界就抛出我们很熟悉的IndexOutOfBoundsException异常。

然后ensureCapacityInternal确保有位置来存放e,同上。

然后把包括index之后的元素复制到往后一位,导致了element[index]空了出来,然后element[index] = element;赋值。

size++把长度加一

 PSSystem.arraycopy(...)的源码:

public static native void arraycopy(Object src,  int  srcPos,

                                        Object dest, int destPos,

                                        int length);

是一个本地方法,使用的是内存复制,省去了大量的数组寻址访问等时间,便于大数组的copy

 

批量增加addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

        System.arraycopy(a, 0, elementData, size, numNew);

        size += numNew;

        return numNew != 0;

}

c toArray

跟上面差不多,一个套路。

 

批量增加addAll(int index, Collection<? extends E> c)

public boolean addAll(int index, Collection<? extends E> c) {

        rangeCheckForAdd(index);

        Object[] a = c.toArray();

        int numNew = a.length;

        ensureCapacityInternal(size + numNew);  // Increments modCount

 

        int numMoved = size - index;

        if (numMoved > 0)

            System.arraycopy(elementData, index, elementData, index + numNew,

                             numMoved);

 

        System.arraycopy(a, 0, elementData, index, numNew);

        size += numNew;

        return numNew != 0;

}

就有一点不同的,如果size == index(相当于在源list紧跟其后添加c),就不用移动了。

PS:不可能出现size < index,此种情况在rangeCheckForAdd(index)处早已屏蔽。

PSarrayList是可以add(null)的。打印的时候打印[null]

 

修改 E set(int index,E element)

public E set(int index, E element) {

        rangeCheck(index);

 

        E oldValue = elementData(index);

        elementData[index] = element;

        return oldValue;

}

代码阅读简单,执行又快速。这也是查找修改用数组的好处。

删除 boolean remove(Object o)

public boolean remove(Object o) {

        if (o == null) {

            for (int index = 0; index < size; index++)

                if (elementData[index] == null) {

                    fastRemove(index);

                    return true;

                }

        } else {

            for (int index = 0; index < size; index++)

                if (o.equals(elementData[index])) {

                    fastRemove(index);

                    return true;

                }

        }

        return false;

}

如果找不到o,只会返回false,并不会报异常。

并且只会remove掉第一个跟o.equals == true的对象。

 

Q&A

Q:为什么空与非空的删除要分开呢?

A:因为要调用o.equals来匹配相应的objectnull会报空指针异常。

private void fastRemove(int index) {

        modCount++;

        int numMoved = size - index - 1;

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index,

                             numMoved);

        elementData[--size] = null; // clear to let GC do its workGC对付最后一个值

    }

0开始的索引,index+1=size代表这个索引是最后一个值的

所以,如果size = index+1,代表要删除最后一个,那么直接elementData[--size] = null就好了;如果不是最后一个,那么就让[index+1位置的元素][最后的元素]复制到[index][最后-1]的位置,然后elementData[--size] = null把最后一个值设置为空,删除完毕。

注意:

--size--size是先执行size=size-1,然后再使用size的值 这时的size值就是表达式--size的值。

 size-- 是先使用size的值作为表达式size--的值,然后,执行size=size-1操作。

这种删除是通过遍历实现的。

 

删除E remove(int index)

public E remove(int index) {

        rangeCheck(index);

 

        modCount++;

        E oldValue = elementData(index);

 

        int numMoved = size - index - 1;

        if (numMoved > 0)

            System.arraycopy(elementData, index+1, elementData, index,

                             numMoved);

        elementData[--size] = null; // clear to let GC do its work

 

        return oldValue;

}

首先检查是否越界。

后面就跟fastRemove差不多了。

 

批量删除boolean removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {

        Objects.requireNonNull(c);

        return batchRemove(c, false);

}

首先检查是否为空。

然后批量删除

public static <T> T requireNonNull(T obj) {

        if (obj == null)

            throw new NullPointerException();

        return obj;

    }

private boolean batchRemove(Collection<?> c, boolean complement) {

        final Object[] elementData = this.elementData;

        int r = 0, w = 0;

        boolean modified = false;

        try {

            for (; r < size; r++)

                if (c.contains(elementData[r]) == complement)

                    elementData[w++] = elementData[r];

        } finally {

            // Preserve behavioral compatibility with AbstractCollection,

            // even if c.contains() throws.

            if (r != size) {

                System.arraycopy(elementData, r,

                                 elementData, w,

                                 size - r);

                w += size - r;

            }

            if (w != size) {

                // clear to let GC do its work

                for (int i = w; i < size; i++)

                    elementData[i] = null;

                modCount += size - w;

                size = w;

                modified = true;

            }

        }

        return modified;

    }

批量删除。

假设list = [1,2,3,4,5,6]c = [2,4,5]

首先建立一个final的数组引用指向当前引用,遍历elementData数组,如果发现c中没有elementData[r]元素(当前elementData[r]元素为非删除元素),那么就把它按照elementData[w++]的顺序从elementData[0]开始覆盖,覆盖完毕后list=[1,3,6,4,5,6],然后如果r!=size(意味着try有异常抛出,r没有涨到size的值),使用System.arraycopy出错时r位置后面的(包括r)值 复制到出错位置w位置的后面(包括w,相当于手动复制过来。

If(w!=size)是用来把w后面的值置空的。

PSsize -= i - 3 = size - (i - 3)

 

modCount

此处为转载

作者:特立独行的猪手
链接:https://juejin.im/post/58edf7cbb123db43cc36f47f
来源:掘金

 

 

addremove过程中,经常发现会有modCount++或者modCount--操作。这里来看下modCount是个啥玩意。

 

modCount变量是在AbstractList中定义的。

    protected transient int modCount = 0;

modCount是一个int型变量,用来记录ArrayList结构变化的次数。

 

modCount起作用的地方是在使用iterator的时候。ArrayListiterator方法。

    public Iterator<E> iterator() {

        return new 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;

 

        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();

        }

    }

iterator方法会返回私有内部类Itr的一个实例。这里可以看到Itr类中很多方法,都会调用checkForComodification方法。来检查modCount是够等于expectedModCount。如果发现modCount != expectedModCount将会抛出ConcurrentModificationException异常。

 

这里写一个小例子来验证体会下modCount的作用。简单介绍一下这个小例子:准备两个线程t1t2,两个线程对同一个ArrayList进行操作,t1线程将循环向ArrayList中添加元素,t2线程将把ArrayList元素读出来。

 

Test类:

public class Test {

 

    List<String> list = new ArrayList<String>();

 

 

    public Test() {

 

    }

 

 

    public void add() {

 

        for (int i = 0; i < 10000; i++) {

            list.add(String.valueOf(i));

        }

 

    }

 

    public void read() {

 

        Iterator iterator = list.iterator();

        while (iterator.hasNext()) {

            System.out.println(iterator.next());

        }

 

    }

t1线程:

public class Test1Thread implements Runnable {

 

    private Test test;

 

    public Test1Thread(Test test) {

        this.test = test;

    }

 

    public void run() {

 

        test.add();

 

    }

t2线程:

public class Test2Thread implements Runnable {

 

    private Test test;

 

    public Test2Thread(Test test) {

        this.test = test;

    }

 

 

    public void run() {

        test.read();

    }

}

main

    public static void main(String[] args) throws InterruptedException {

 

        Test test = new Test();

        Thread t1  = new Thread(new Test1Thread(test));

        Thread t2  = new Thread(new Test2Thread(test));

 

        t1.start();

        t2.start();

 

    }

执行这个mian类就会发现程序将抛出一个ConcurrentModificationException异常。

 

由异常可以发现抛出异常点正处于在调用next方法的checkForComodification方法出现了异常。这里也就出现上文描述的modCount != expectedModCount的情况,原因是t2线程在读数据的时候,t1线程还在不断的添加元素。

这里modCount的作用也就显而易见了,用modCount来规避多线程中并发的问题。由此也可以看出ArrayList是非线程安全的类。

原创粉丝点击