ArrayList源码分析

一、ArrayList简介

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。

ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。

ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。

和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

二、源码分析

一、属性:

  1. 默认初始容量大小
private static final int DEFAULT_CAPACITY = 10;
  1. 空数组(用于空实例)。
private static final Object[] EMPTY_ELEMENTDATA = {};
  1. 用于默认大小空实例的共享空数组实例。我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  1. elementData数组用来存储ArrayList中的元素,从这个可以看出,ArrayList是底层是借组于数组来实现的。
transient Object[] elementData; 
  1. size用来记录ArrayList中存储的元素的个数。
private int size;

二、构造方法:

  1. 带初始容量参数的构造函数。(用户自己指定容量)
	public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        	//创建initialCapacity大小的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	//创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  1. 默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  1. 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
	public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray 可能返回的不是Object类型的数组所以加上下面的语句用于判断,
            //这里用到了反射里面的getClass()方法
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

上面将容器Collection转化为数组赋给数组elementData,还对Collection转化是否转化为了Object[]进行了检查判断。如果Collection为空,则就将空的常量数组对象EMPTY_ELEMENTDATA赋给了elementData;

三、add方法:

  1. add(E e):将元素加入到List的末尾。
	public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

上面代码调用了另一个add方法

	private void add(E e, Object[] elementData, int s) {
		//s为size,当size等于数组长度时,数组扩容
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
  1. add(int index, E element):将element插入index位置
	public void add(int index, E element) {
		//对index进行界限检查
        rangeCheckForAdd(index);
        modCount++;
        final int s;
        Object[] elementData;
        //当size等于数组长度时,数组扩容
        if ((s = size) == (elementData = this.elementData).length)
            elementData = grow();
        //arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }
  1. addAll(Collection<? extends E> c):按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        modCount++;
        int numNew = a.length;
        if (numNew == 0)
            return false;
        Object[] elementData;
        final int s;
        if (numNew > (elementData = this.elementData).length - (s = size))
            elementData = grow(s + numNew);
        System.arraycopy(a, 0, elementData, s, numNew);
        size = s + numNew;
        return true;
    }

四、扩容核心方法:

ArrayList的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。

  1. 要分配的最大数组大小
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  1. 最少扩容为size+1
    private Object[] grow() {
        return grow(size + 1);
    }
  1. 把数组复制到扩容后的数组
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }
  1. 扩容核心
    private int newCapacity(int minCapacity) {
        // oldCapacity为旧容量,newCapacity为新容量
        int oldCapacity = elementData.length;
        //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
        //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
        if (newCapacity - minCapacity <= 0) {
        	//数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA时,那第一次增加元素时扩容为10
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        //再检查新容量是否超出了ArrayList所定义的最大容量,
        //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
        //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
    
	//比较minCapacity和 MAX_ARRAY_SIZE
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }

四、remove方法

1.remove(int index):删除index的元素

    public E remove(int index) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }

上面的代码用到了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;// clear to let GC do its work
    }
  1. remove(Object o):移除指定元素
	public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
        	//如果为null,遍历与null比较
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
            	//如果不为空,遍历与o比较
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }
  1. removeAll(Collection<?> c):从此列表中删除指定集合中包含的所有元素。
	public boolean removeAll(Collection<?> c) {
        return batchRemove(c, false, 0, size);
    }

五、get方法

get(int index):获得index出的值

    public E get(int index) {
    	//边界检查
        Objects.checkIndex(index, size);
        return elementData(index);
    }

六、set方法

set(int index, E element):将element复制给index

    public E set(int index, E element) {
        Objects.checkIndex(index, size);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

七、clear方法

clear():清除ArrayList中所有的元素。直接将数组中的所有元素设置为null即可,这样便于垃圾回收。

    public void clear() {
        modCount++;
        final Object[] es = elementData;
        for (int to = size, i = size = 0; i < to; i++)
            es[i] = null;
    }

三、几个问题

一、System.arraycopy()和Arrays.copyOf()方法

联系: 看两者源代码可以发现copyOf()内部调用了System.arraycopy()方法

区别:

  1. arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置

  2. copyOf()是系统自动在内部新建一个数组,并返回该数组。

二、ArrayList的交集,差集,并集,去重并集

  1. 交集:list2.retainAll(list1),此时list2中只包含之前list1list2共有的元素(交集),如果lis2中的元素有被修改过,会返回true,否则返回false

  2. 差集:list2.removeAll(list1);,此时list2中只包含之前属于list2而不属于list1的元素,如果lis2中的元素有被修改过,会返回true,否则返回false

  3. 并集:list2.addAll(list1);,此时list2中包含之前list1list2中的元素,如果lis2中的元素有被修改过,会返回true,否则返回false

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值