JDK1.8--ArrayList源码分析

JDK1.8--ArrayList源码分析,第1张

一:基本介绍

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
ArrayList继承于AbstractList类,实现了List接口;他是一个数组队列,提供了相关的添加、删除、遍历的功能。
ArrayList实现了RandomAccess接口,说明其提供了随机访问的功能;RandomAccess接口是一个标记接口,用以标记实现的List集合具备快速随机访问的能力。所有的List实现都支持随机访问的,只是基于基本结构的不同,实现的速度不同罢了,这里的快速随机访问,那么就不是所有List集合都支持了。
ArrayList基于数组实现,数组带下标,可以实现常量级的随机访问,复杂度O(1).
LinkedList基于链表实现,随机访问需要依靠遍历实现,复杂度为O(n)
ArrayList实现了Serializable接口,这意味着ArrayList 支持序列化,能通过序列化去传输。
ArrayList实现了Cloneable接口,能被克隆。
其于Vector不同的是Vector是线程安全的,因此建议在单线程中才使用ArrayList,多线程中使用Vector。

结构图如下:

二.源码解析

ArrayList源码如下:
1.基本属性:

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;

    /**
     * 共享的空对象
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     *一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值;区分上面的EMPTY_ELEMENTDATA,这个第一次添加add时会膨胀(扩容长度为10)
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存添加到ArrayList中的元素。 
	 * ArrayList的容量就是该数组的长度。 
	 * 该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入
	 * ArrayList中时,数组将扩容值DEFAULT_CAPACITY。 
	 * 被标记为transient,在对象被序列化的时候不会被序列化。
     */
    transient Object[] elementData; // non-private to simplify nested class access
    /**
     * 数组长度
     * @serial
     */
    private int size;
	/**
     * 最大数组容量
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.构造函数
2.1:无参构造

	/**
     * 构造一个初始容量为10的空列表
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

说明:构造一个容量为10的空列表,在创建时不传入参数,使用当前的无参构造去创建,前面的属性得知DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object[],将elementData 初始化,elementData 也是个Object[], 属性介绍上表示空的elementData 会给初始容量为10,那是因为第一次add()时给了初始化容量10;
2.2:带int的有参构造

/**
     *构造具有指定初始容量的空列表
     *
     * @param  initialCapacity  列表的初始容量
     * @throws IllegalArgumentException 如果指定的初始容量是负数
     */
    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);
        }
    }

说明:当initialCapacity大于等于0时,那么initialCapacity为ArrayList的初始化长度,当initialCapacity小于0时,报错:参数不合法;

2.3:带Collection对象的构造函数

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下。
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

说明:
1.构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。第二个有参构造方法构造时赋的值是它的父类Collection对象。
2.将collection对象转换成数组,然后将数组的地址的赋给elementData。
3.如果数组的实际大小等于0(c中没有元素),将空数组EMPTY_ELEMENTDATA赋值给elementData
4.如果size的值大于0,则执行Arrays.copy方法,把collection对象的内容(可以理解为深拷贝)copy到elementData中。

3.方法
3.1:add方法
add方法分为两个,一个参数和两个参数的
3.1.2:add(E e)
添加元素方法入口:

/**
     * Appends the specified element to the end of this list.
     * 将指定的元素追加到此列表的末尾
     *
     * @param e element to be appended to this list
     * @return true (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    	// 确认list容量,如果不够,容量加1。
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
	//数组容量检查,不够时则进行扩容,只供类内部使用 
	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	//确保添加的元素有地方存储,当第一次添加元素的时候this.size+1 的值是1,所以第一次添加的时候会将当前elementData数组的长度变为10:保证添加的元素有地方存储。
	private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	//容量检查,将修改次数(modCount)自增1,判断是否需要扩充数组长度,判断条件就是用当前所需的数组最小长度与数组的长度对比,如果大于0,则增长数组长度。	
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        // 最小容量>数组缓冲区当前长度
        if (minCapacity - elementData.length > 0)
        	// 增加数组长度
            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);
    }

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

说明:
1.添加单个元素时,会先进行容量检查。此时将size+1,保证加入的对象有空间可以存储。ensureCapacityInternal(int minCapacity)此时的minCapacity为size+1;
2.再进行计算加入的数据要多少容量能容纳,ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));调用这个方法,其中calculateCapacity(elementData, minCapacity)计算容量,如果elementData为空,那么此时返回默认容量大小为10;否则则返回minCapacity。
3.modCount++;修改次数加1;如果最小的容量大于了当前list的容量大小,将进行扩容;

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

调用grow进行扩容。
扩容机制如下:

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 原基础上扩大1.5倍
        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);
    }

    private static int hugeCapacity(int minCapacity) {
    	//如果minCapacity<0,抛出异常
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //如果想要的容量大于MAX_ARRAY_SIZE则分配Integer.MAX_VALUE:2147483647,否则分配MAX_ARRAY_SIZE:2147483639。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

扩容,保证ArrayList至少能存储minCapacity个元素 ,第一次扩容,逻辑为newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基础的1.5倍。 第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。
3.1.3:add(int index, E element)

/**
     * 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).
     *在此列表的指定位置插入指定元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引上加1)
     * @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) {
    	// 范围检查;如果index的位置超出了数组的大小。或者小于0,抛异常IndexOutOfBoundsException
        rangeCheckForAdd(index);
		// 检查是否需要扩容,跟添加一个元素类似;
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //确保有足够的容量之后,使用System.arraycopy 将需要插入的位置(index)后面的元素统统往后移动一位。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 插入赋值
        elementData[index] = element;
        size++;
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

3.1.4:addAll(Collection 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;
    }

说明:主要涉及数组的容量扩容,新的list的大小为原数组大小+插入的集合的空间大小。
3.1.5:addAll(int index, Collection 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)
        // 从传入的下标位置,原数组在index位置开始往后移
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
		// 插入collection
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

3.2:get,set方法
3.2.1:get(int index)

public E get(int index) {
		// 检查数组下标,大于 等于了数组大小,抛异常IndexOutOfBoundsException
        rangeCheck(index);
		// 返回当前下标位置的元素
        return elementData(index);
    }

3.2.2:set(int index, E element)
确保set的位置小于当前数组的长度(size)并且大于0,获取指定位置(index)元素,然后放到oldValue存放,将需要设置的元素放到指定的位置(index)上,然后将原来位置上的元素oldValue返回给用户。

public E set(int index, E element) {
		// 检查数组下标,大于 等于了数组大小,抛异常IndexOutOfBoundsException;确保有容量;
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

3.3:remove方法
3.3.1:remove(int index)

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *删除位于列表中指定位置的元素。将所有后续元素左移(从其下标减去1)。
     * @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++;
        // 将要删除的元素给oldValue
        E oldValue = elementData(index);
		// 得到删除后,需要往左移的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 拷贝覆盖
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
            // 清理内存空间,给GC回收
        elementData[--size] = null; // clear to let GC do its work
		// 返回旧值
        return oldValue;
    }

说明:删除时:1.先检查数组下标;
2.将删除的元素赋给oldValue;
3.找出要向左移的元素个数
4.进行拷贝覆盖
5.数组最后一位置空,方便GC回收
6.返回旧值
3.3.2: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;
    }
    
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
    }

说明:根据对象来删除元素,分为两种情况:
1.需要删除的对象为null的情况
1.1:遍历判断对象是否为空,为空则调用fastRemove(index)方法进行删除;
2.需要删除的对象不为null的情况,遍历判断得到当前数组中与删除的对象相同的值,调用fastRemove(index);进行删除。
3.3.3:removeRange(int fromIndex, int toIndex)

// 删除下标fromIndex(包含一起删了)到toIndex(保留不删)的数据
protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        // 得到要左移的元素个数
        int numMoved = size - toIndex;
        // 拷贝覆盖
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        // 从什么位置开始释放空间
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

3.3.4:clear()方法

//从列表中删除所有元素。此调用返回后,列表将为空
public void clear() {
        modCount++;
		// 遍历置空,给GC回收
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

3.4:其他方法:
3.4.1:indexOf(Object o)
返回o第一次出现的下标为,如果没得则返回-1;同时得区分传入的对象是否为空的情况,分开判断。

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index i such that
     * (o==null ? get(i)==null : o.equals(get(i))),
     * or -1 if there is no such index.
     * 返回列表中指定元素第一次出现的索引,如果列表中不包含该元素,则返回-1。更正式地,返回最低索引,如果没有这样的索引,则返回-1。
     */
    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;
    }

3.4.2:contains(Object o)
直接调用indexOf(o)方法,得到当前对象出现的索引位置,再与0判断大小。返回boolean 类型

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

三、总结

1)arrayList可以存放null。
2)arrayList本质上就是一个elementData数组。
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法。
4)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,LinkedList较快 。
5)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/719628.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存