jdk1.8之简述ArrayList实现原理

1.ArrayList概述

1.1ArrayList特点

  • ArrayList是实现List接口的动态数组
  • ArrayList可以允许元素为Null
  • ArrayList是线程不安全

1.2ArrayList数据结构

在这里插入图片描述
ArrayList底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据

2.ArrayList源码分析

2.1属性

private static final int DEFAULT_CAPACITY = 10;   // 默认大小为10,
		实际上调用无参构造函数初始化时,存放数据的数据是个空数组,
		调用add时才会真正初始化.
private static final Object[] EMPTY_ELEMENTDATA = {}; 
		 // 使用带参构造器进行初始化,若initcapacity为0,或者传入的集合
			为空时,会指向该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
		// 调用无参构造函数时,使用该数组
transient Object[] elementData;  // 真正存放数据的数组
private int size;  // 数组元素的个数

2.2构造方法

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的构造方法,若initialCapacity=0,则elementData 指向空数组;若elementData >0,则elementData进行初始化

public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

默认构造方法。执行默认空数组

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;
    }
}

带参数的构造方法,可以传入一个集合。若集合中没有元素,则指向空数组;否则,就把集合中的元素copy到elementData数组中。底层调用了本地方法System.arraycopy()。

2.3主要方法

2.3.1add():将此元素添加到列表的末尾
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

先判断下当前elementData数组能否容纳下新增的元素,如果空间不够的话,需要先扩容,然后再插入元素。
size是数组中数据的个数,因为要添加一个元素,所以,要先判断数组能否装下(size+1)个元素

private void ensureCapacityInternal(int minCapacity) {
   ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

calculateCapacity()方法是用来计算容量的。先判断elementData是否是默认空数组(使用无参构造器构造的)

  • 如果elementData是默认空数组,则这是第一次添加元素,那么size=0,minCapacity=size+1=1,通过调用Math.max()方法,会返回minCapacity=DEFAULT_CAPACITY=10
  • 如果elementData是空数组,则这是第一次添加元素,那么size=0,minCapacity=size+1=1,不会调用Math.max()方法,会返回minCapacity=1
  • 如果elementData不是空数组且不是默认空数组,那么size!=0,minCapacity=size+1,不会调用Math.max()方法,会返回minCapacity=size+1
// 确定是否扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    
    if (minCapacity - elementData.length > 0)
        // 扩容
        grow(minCapacity);
}

modCount:操作数(父类AbstractList中的属性)表示对这个ArrayList进行操作过了。
在ensureExplicitCapacity()方法中,首先操作数自增1,把需要的最小空间容量与数组当前实际长度进行比较:

  • 如果elementData是默认空数组,它现在需要的容量是10,但elementData.length=0,所以,需要扩容
  • 如果elementData是空数组,它现在需要的容量是1,但elementData.length=0,所以,需要扩容
  • 如果elementData是非空数组,若它添加一个元素后需要的容量比原数组长度大,就需要扩容;否则就不需要扩容。
 private void grow(int minCapacity) {
   
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    elementData = Arrays.copyOf(elementData, newCapacity);
}

grow()方法进行扩容,保证ArrayList至少能存储minCapacity个元素 。
第一次扩容,newCapacity为原先的1.5倍.
第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。如果此时capacity已经超过了ArrayList的最大容量Integer.MAX_VALUE – 8,那么newCapacity最大为Integer.MAX_VALUE.

然后调用Arrays.copyOf(elementData, newCapacity)对原先旧的数组进行复制操作,底层调用的是System.arraycopy,这是个native方法.

所以,要尽量避免扩容,数组的复制非常耗费性能!而且旧的数组需要GC!

发布了78 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Lucky_Boy_Luck/article/details/104355908