ArrayList底层原理剖析

ArrayList实现原理和对应优缺点ArrayList底层是基于数组来实现的。由于Java数组是定长数组,一旦声明了数组,数组长度就是固定的,存储元素的个数就是固定的。没办法改变。 => 所以ArrayList自动扩容的实现方式只能

ArrayList底层原理剖析

ArrayList实现原理和对应优缺点

ArrayList底层是基于数组来实现的。由于Java数组是定长数组,一旦声明了数组,数组长度就是固定的,存储元素的个数就是固定的。没办法改变。

=> 所以ArrayList自动扩容的实现方式只能是重新声明一个长度更大的数组,并且把旧数组的元素拷贝到新数组中

=> 所以说频繁的往ArrayList存放元素,就会涉及到数组扩容和元素拷贝,这个过程相对来说性能并不是很好

到这里就引申出ArrayList的一个缺点:就是频繁往ArrayList中塞入数据可能会导致性能下降

还有一点就是往ArrayList指定位置插入或者删除元素,由于底层就是基于数组来实现,所以往数组指定位置插入元素,该位置后面的元素就要往后移动。删除元素,该位置开始后面的元素就要往前移动,频繁插入或删除元素,性能会有所下降。这也是ArrayList的一个缺点

说完缺点再说优点,数组是支持随机读的,也就是查询到数组中任意位置的元素。这个过程是基于内存地址来快速定位到元素的,查询性能很高,所以说ArrayList是支持随机读的,可以快速读取到集合中的任意一个元素

ArrayList的应用场景

基于ArrayList的优缺点,不难发现ArrayList更多的是适合读大于写的场景。也就是往ArrayList存放一批数据,后面就不会往ArrayList中频繁的插入或者删除元素,更多的是查询ArrayList的元素。

ArrayList的元素是有顺序的,如果业务需要按照一定顺序去存放元素到集合当中,并且后面不会频繁的去插入新元素而是更多的去读取元素,那么ArrayList是非常合适的选择

ArrayList的核心API

代码语言:java复制
public class ArrayListDemo {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>(50);
        // 存放元素
        list.add("张三");
        list.add("李四");
        list.add("王五");
        // 随机读
        System.out.println(list.get(1));
        // 移除指定位置的元素
        list.remove(1);
        // 获取元素个数
        System.out.println(list.size());
        // 修改指定位置的元素值
        list.set(0, "赵六");
        // 指定索引位置添加元素
        list.add(1, "小明");
        // 遍历ArrayList
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

ArrayList源码分析

ArrayList构造方法的源码分析

=> 使用无参的构造方法创建对象,则是让ArrayList底层引用一个空的数组,长度为0。容量大小默认是10;

=> 使用有参的构造方法创建对象,则是校验入参合法性,如果大于0则是创建一个指定长度的,元素类型为Object类型 的数组

=> 对ArrayList玩的比较熟的,基本上都会使用有参构造方法来创建ArrayList。如果存储的元素个数比较多,使用无参 构造方法创建ArrayList,默认容量只有10,所以会直接执行好几次数组扩容和元素拷贝,性能不是很高

代码语言:java复制
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 初始化容量,默认是10
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 这个是ArrayList底层引用的数组。元素类型是Object类型的一个数组
    transient Object[] elementData; // non-private to simplify nested class access
    // 存放的元素个数,默认是0
    private int size;

    // 有参构造方法,给ArrayList指定容量
    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);
        }
    }
    // 无参构造方法,没有指定容量,默认的容量大小为DEFAULT_CAPACITY,也就是10
    public ArrayList() {
        // 使用无参构造方法,给elementData赋予一个空数组,没有元素
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
ArrayList的add()方法的源码分析

add()是直接在ArrayList中添加一个元素。调用add方法添加元素的时候,则是先判断容量是否充足, ensureCapacityInternal这个后面讲。在容量充足的前提下,添加元素实际上就是往底层维护的数组当中去添加一个元素,添加完再让数组元素个数+1

代码语言:java复制
  public boolean add(E e) {
        // 这个是判断容量是否充足的方法,涉及到数组扩容和元素拷贝,是ArrayList的核心方法
        ensureCapacityInternal(size + 1);
        // 直接给数组添加元素,并给元素个数+1 
        elementData[size++] = e;
        return true;
    }
ArrayList的get()方法的源码分析

这个方法是根据索引获取元素,由于ArrayLIs底层是一个Object数组,根据索引来获取数组中的元素,其实就是基于内存地址直接定位到元素的位置,性能很高,也是ArrayList的一个优势

代码语言:java复制
 public E get(int index) {
       // 校验索引是否越界,有则抛出一个数组索引越界异常
        rangeCheck(index);

       // 直接根据索引到数组中获取元素,性能很高
        return elementData(index);
    }

 E elementData(int index) {
        return (E) elementData[index];
    }
ArrayList的add(int,E)方法的源码分析

指定索引位置去插入一个元素。核心就是System.arraycopy(elementData, index, elementData, index + 1, size - index)。就是从指定插入元素位置开始的所有元素,全部往后挪动一个位置,然后再把原来的元素改为要插入的元素值

代码语言:java复制
public void add(int index, E element) {
       // 校验索引是否越界
        rangeCheckForAdd(index);
       // 判断容量是否是否充足,是否需要扩容
        ensureCapacityInternal(size + 1); 
       // 把数组中索引位置为index开始,拷贝到原数组中以index+1未开始的位置,拷贝size-index元素
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
       // 数组元素往后挪动一位后,让index位置的元素改为传入的元素值
        elementData[index] = element;
        size++;
    }
ArrayList的remove(int)方法的源码分析

这个方法和上面的方法差不多,核心就是 System.arraycopy(elementData, index+1, elementData, index,numMoved)。就是把要删除元素的索引位置开始,全部往前面挪动一位。最后再把最后一个元素改为null

代码语言:java复制
 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);
       // 元素往前移动后,最后一个元素改为NULL
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
ArrayList的set(int,E)方法的源码解析

修改指定索引位置的元素值。因为ArrayList底层就是Object数组,所以直接基于内存地址去修改的,所以执行这个方法的性能也是很高的。

代码语言:java复制
 public E set(int index, E element) {
     	// 校验索引是否越界
        rangeCheck(index);
     	// 获取旧元素的值
        E oldValue = elementData(index);
        // 改为新元素的值
        elementData[index] = element;
        return oldValue;
    }
ArrayList的扩容方法源码解析

=> 判断ArrayList引用是否为空数组,如果是则赋予初始化容量为10

=> 如果引用不是空数组,就判断目前元素个数是否超过了数组长度

=> 如果超过数组长度,就重新设置数组容量,大小是旧数组容量的1.5倍

=> 执行Arrays.copyof()创建一个新容量的数组,并且把旧数组的元素拷贝到新数组当中

代码语言:java复制
// minCapacity是添加新元素之前的数组元素个数+1
private void ensureCapacityInternal(int minCapacity) {
    	// 判断elemenetData是否为{}
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 如果是,就直接赋予DEFAULT_CAPACITY,也就是之前说的默认初始化容量10
            // 使用无参构造方法创建对象,第一次调用add方法就会执行到这里,给数组默认为10的长度
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    	// 扩容的核心逻辑
        ensureExplicitCapacity(minCapacity);
    }

  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 判断最小容量是否大于目前数组长度,如果是,才需要扩容
        if (minCapacity - elementData.length > 0)
            // 扩容的核心逻辑
            grow(minCapacity);
    }

 private void grow(int minCapacity) {
        // 获取旧数组长度
        int oldCapacity = elementData.length;
        // 旧数组长度右移一位,想当于除以2,再加上旧数组长度,等于新数组长度
        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);
    }

发布者:admin,转转请注明出处:http://www.yc00.com/web/1748202073a4746953.html

相关推荐

  • ArrayList底层原理剖析

    ArrayList实现原理和对应优缺点ArrayList底层是基于数组来实现的。由于Java数组是定长数组,一旦声明了数组,数组长度就是固定的,存储元素的个数就是固定的。没办法改变。 => 所以ArrayList自动扩容的实现方式只能

    4小时前
    10

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信