美文网首页
jdk源码分析之ArrayList

jdk源码分析之ArrayList

作者: shoulda | 来源:发表于2018-05-05 10:27 被阅读0次

ArrayList关键字段分析

  /**

     * The array buffer into which the elements of the ArrayList are stored.

     * The capacity of the ArrayList is the length of this array buffer. 

     */

    transient Object[] elementData; // non-private to simplify nested class access

    /**

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

     * @serial

     */

    private int size;

ArrayList用Object[] 数组来存放元素,
size表示数组里面存放了多少元素

  /**

     * Shared empty array instance used for default sized empty instances. We

     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when

     * first element is added.

     */

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

多个ArrayList实例共享的static属性,一个空数组的实例,使用ArrayList的无参构造函数创建ArrayList实例的时候,直接使DEFAULTCAPACITY_EMPTY_ELEMENTDATA给底层数组elementData赋值

/**

     * Constructs an empty list with an initial capacity of ten.

     */

    public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }

而当创建一个容量为0的ArrayList时,直接将层数组elementData赋值为另外一个static属性.

从ArrayList获取数据get(int index)

  public E get(int index) {

        rangeCheck(index);

        return elementData(index);

    }

private void rangeCheck(int index) {

        if (index >= size)

            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

get方法很简单,输入参数合法的话直接返回底层数组elementData对应位置的元素,而rangeCheck主要检查下标不能越界访问。

ArrayList尾部添加一个数据add(E e)

  public boolean add(E e) {

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

        elementData[size++] = e;

        return true;

}

添加数据时,首先要检查是否需要扩容来添加新的数据,调用ensureCapacityInternal()来确当前容量足够添加一个新的数据,然后elementData[size++] = e将新数据添加在数组的末尾。

private void ensureCapacityInternal(int minCapacity) {

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

        }

        ensureExplicitCapacity(minCapacity);

 }

ensureCapacityInternal确保在ArrayList容量为0的时候添加数据扩容时,至少扩容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小默认为10.

接着调用ensureExplicitCapacity(minCapacity)进行扩容

  private void ensureExplicitCapacity(int minCapacity) {

        modCount++;

        // overflow-conscious code

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

modCount++用于记录修改次数,主要用于一个线程在迭代数据的时候,另一个数据更改ArrayList结构抛出的ConcurrentModificationException异常。if (minCapacity - elementData.length > 0)用于判断是否真的需要扩容, elementData.length表示的是容器容量,size表示容器存储数据的数量。

调用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);

    }

可以看到扩容时直接将容量大小变为之前的1.5倍

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

这里还需要对newCapacity进行调整,如果扩容后的newCapacity还是比minCapacity小
满足

if (newCapacity - minCapacity < 0)

设置newCapacity为传进来的参数minCapacity

newCapacity = minCapacity;

然后判断现在的newCapacity是否比上限MAX_ARRAY_SIZE大

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

如果newCapacity比MAX_ARRAY_SIZE还大,则需要调用hugeCapacity(minCapacity)判断是否是minCapacity传入负数溢出

private static int hugeCapacity(int minCapacity) {

        if (minCapacity < 0) // overflow

            throw new OutOfMemoryError();

        return (minCapacity > MAX_ARRAY_SIZE) ?

            Integer.MAX_VALUE :

            MAX_ARRAY_SIZE;

    }

hugeCapacity(int minCapacity)首先检测是否溢出,没溢出的话就返回Integer.MAX_VALUE 作为最大值(minCapacity > MAX_ARRAY_SIZE条件在调用出就满足),通过上面的步骤,最终得到满足条件的minCapacity.

elementData = Arrays.copyOf(elementData, newCapacity);

这里把之前elementData数组的值复制到新数组newCapacity中。

向ArrayList任意位置添加一个数据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++;

    }

通过add(int index,E element)在ArrayList任意位置添加数据。
rangeCheckForAdd()函数是检查参数的合法性。
然后ensureCapacityInternal()继续进行扩容,扩容的原理上面已经分析,接着扩容后就是调用System.arraycopy进行数组的复制,由此可见ArrayList添加数据是比较耗费性能的,事件复杂度是O(n),因为插入数据总是伴随着数组的复制(数组元素的移动)。

在ArrayList尾部添加一个Collection集合addAll

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;

    }

通过Collection的toArray方法把Collectionz转化为Object数组,然后通过ensureCapacityInternal()进行扩容,最后System.arraycopy进行数组的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示从数组a的下标0出开始复制length个元素导数组elementData的下标size处。

ArrayList在任意位置添加一个集合Collection

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;

}

rangeCheckForAdd(int index)做入口参数的合法性检查
然后把集合转化成数组
ensureCapacityInternal(size + numNew)进行扩容
计算原数组需要移动位置的个数int numMoved = size - index
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);从elementData数组下标为index处复制numMoved个数据到elementData数组的下标为index + numNew处,这样elementData数组下标为index到index+numNew-1的位置被空出来,用于放置新的数据然后集合的数据添加到elementData数组留出的位置即可。

ArrayList中获取某一个数据的下标indexOf

public int indexOf(Object o) {

        if (o == null) {

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

                if (elementData[i]==null)

                    return i;

        } else {

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

                if (o.equals(elementData[i]))

                    return i;

        }

        return -1;

    }

获取下标只能通过遍历的方式逐一比较,null数据不能调用方法,所以不能通过equals进行比较;如果传入的参数o是null的话,通过elementData[i] == null进行比较,否则通过o.equals(elementData[i]) 进行比较。如果遍历过程中找到该数据,返回该数据下标,遍历结束没有找到返回-1 。

ArrayList删除指定位置的数据

 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;

}

代码流程还是比较清晰 下标检查。
elementData[–size] = null; // clear to let GC do its work
自己申请内存自己要记得管理,否则造成内存泄漏。

ArrayList删除某一个数据

    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是否为null进行分类处理,找到index所在的位置后调用fastRemove()进行删除。

相关文章

网友评论

      本文标题:jdk源码分析之ArrayList

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