Java ArrayList的源码解析(jdk1.8)

创建

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

这是我们熟悉的创建方法,进去看看。

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

        代码很简单,elememtData其实就是用来存储插入的元素的。可以看到,默认的创建方式elementData指向了一个空数组,也就是说ArrayList默认大小为0。(注意这是jdk1.8的源码,jdk1.8以前默认大小为10,感兴趣的朋友可以看看这篇文章),再看看另外一种创建方式

/**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    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;
        }
    }

        代码也很简单,如果传进来的是非空集合,把这个集合的所有元素拷贝下来,如果是空集合,处理方式就和第一种方法一样。

插入元素

插入元素也有多种方式,先看最基本的。

list.add("str0");
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

可以看到,在真正插入之前要对容量进行检查。跳进ensureCapacityInternal方法看看

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
...
/**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
...

再跳进ensureExplicitCapacity方法看看

private void ensureExplicitCapacity(int minCapacity) {
		//容器改动次数加一
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

        minCapacity其实就是插入操作所需容量的最小值(回到上面的代码可以知道传进来的是当前元素个数+1)。接下来做一个判断,其实就是判断插入后会不会溢出,如果会就跳进grow方法进行扩容,看看grow方法。


    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //1
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //2
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //3
        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);
    }
...
/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
...

        代码1:计算原容量的1.5倍是多少(为什么不用浮点数乘?因为在汇编代码里浮点数乘的操作要比移位和加减操作复杂很多)。
        代码2:如果扩大了1.5倍还是不够,就干脆等于所需容量最小值。
        代码3:如果新容量值超出MAX_ARRAY_SIZE,那么这个时候实际上已经接近溢出的边缘了(MAX_ARRAY_SIZE 仅仅比 Integer.MAX_VALUE 小8),跳进hugeCapacity方法。

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

        从代码可以看出ArrayList最大容量为Integer.MAX_VALUE,不能再多了。至此,插入前的容量检查工作就完成了,返回到原来的位置继续分析。

```java
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

        ensureCapacityInternal方法检查容量没问题后,就可以安心地插入新元素了,源码里并没有进行判空,也就是说add方法是可以插入空值的。再看看另一种插入方式。

list.add(5,"str0");
/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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++;
    }

在真正插入之前,先检查一下插入位置是否合法。

/**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

代码很简单,不合法就抛出异常。
检查没问题后插入即可。

查询

因为元素是使用数组来存储的,所以查询操作就很简单了。

list.get(0);

先检查一下参数是否合法,再查询。

...
/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
...
@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
...

删除

按下标删除

list.remove(5);
/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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;
    }

先进行参数检查,把删除后的所有元素向前移一步。再看看按元素删除。

list.remove("str0");
/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    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;
    }

        ArrayList是允许插入空值的。这种删除操作是把容器数组遍历一遍,删除指定的值。看看fastRemove方法

/*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    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 work
    }

很简单,其实就是数组移位覆盖操作。

其他方法

trimToSize方法

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

        从注释可以知道这个方法可以在内存短缺的时候调用,释放一些多余的空间。注意size和elementData.length的区别,size是元素的个数,elementData.length是容器的大小。
        ArrayList在进行了多次增、删、扩容操作后,最后可能只剩下了几个元素,但数组容量却被扩的很大,大量空白空间留着不用是很浪费的,这个方法就是用来减少不必要空间开支的。

线程安全问题

倒回去再看看ArrayList增删元素的源码,会发现它们并没有上锁解锁操作,也就是说ArrayList是线程不安全的。

参考文章

不同jdk版本下ArrayList的默认容量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值