ArrayList源码理解

本文深入分析了ArrayList的源码,详细介绍了其数据结构、构造方法、添加、查询、删除等核心操作的实现原理,并对比了与LinkedList的不同之处。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArrayList源码理解

导读

LinkedList源码理解放在一起查阅,效果更好

本文先给出ArrayList的特点,再从源码的角度分析为什么会有这些特点:

  1. 对队成员变量的分析,可以知道ArrayList的数据结构
  2. 对add()方法的分析,可以得知ArrayList添加数据的效率不高
  3. 对get()方法的分析,可以看出ArrayList查询的效率非常高
  4. 对remove()方法的分析,可以了解到ArrayList删除数据的效率不高

特点

与LinkedList比较,ArrayList有以下特点:

  1. 查询效率高
  2. 添加和删除的效率不高
  3. 数据结构是数组

源码分析

成员变量

从成员变量中可以看出,ArrayList是用数组Object[]来储存数据的

//容器默认大小
private static final int DEFAULT_CAPACITY = 10;

//空的容器
private static final Object[] EMPTY_ELEMENTDATA = {};

//容器,用来储存数据
private transient Object[] elementData;

//标记容器的大小
private int size;

构造方法

三个重写的构造方法都是用来初始化容器

//构造方法
public ArrayList(int initialCapacity) {
    super();
    //非法参数校验
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    //给定数组的大小                                      
    this.elementData = new Object[initialCapacity];
}

//构造方法
public ArrayList() {
    super();
    //默认为空数组
    this.elementData = EMPTY_ELEMENTDATA;
}

//构造方法
public ArrayList(Collection<? extends E> c) {
    //将给定的集合转换成数组
    elementData = c.toArray();
    //此时,容器内有数据,更新size
    size = elementData.length;
    //c.toArray()出现异常的处理
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

add()的分析

从下面的代码中可以看出,ArrayList是通过数组赋值的方式来添加数据

//添加数据
public boolean add(E e) {
    //更新一些数据(modCount),根据一些情况来扩充容器的大小
    ensureCapacityInternal(size + 1);  
    //添加数据,更新size
    elementData[size++] = e;
    return true;
}

//向指定的位置添加数据
public void add(int index, E element) {
    //非法参数校验
    rangeCheckForAdd(index);

    //更新一些数据(modCount),根据一些情况来扩充容器的大小
    ensureCapacityInternal(size + 1); 
    //从index开始(包括),数据向后移动一位(这里的效率低)
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //添加数据
    elementData[index] = element;
    //更新size
    size++;
}

从下面的代码中可以看出数组的大小是动态变化的,一般扩充倍数为1.5倍

//根据minCapacity来确定容器的大小
private void ensureCapacityInternal(int minCapacity) {
    //当前为空容器的处理
    if (elementData == EMPTY_ELEMENTDATA) {
        //minCapacity取最大值
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    //更新modCount
    modCount++;

    // 如果需要的容量大于当前的容量,扩充容器
    if (minCapacity - elementData.length > 0)
        //扩中容器的大小
        grow(minCapacity);
}

//ARRAY的最大值
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//扩中容器
private void grow(int minCapacity) {
    // 获取当前容器的大小
    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);

    // 数据复制到新容器中,(这里效率不高)
    elementData = Arrays.copyOf(elementData, newCapacity);
}

//处理容量巨大时的情况
private static int hugeCapacity(int minCapacity) {
    //超过int的最大值,抛出异常
    if (minCapacity < 0) 
        throw new OutOfMemoryError();

    //根据情况设置为int的最大值或者MAX_ARRAY_SIZE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

说明:ArrayList在添加数据的时,有时候会移动大量的数据,导致添加数据效率不高

get()的分析

从下面的代码中可以看出,ArrayList在查找数据时,通过index直接锁定内存,可以迅速地找到相应的数据

//获取指定位置上的数据
public E get(int index) {
    //非法参数校验
    rangeCheck(index);

    //返回数据
    return elementData(index);
}

remove()的分析

移除指定位置上的数据:remove(int index)

//移除指定位置上的数据
public E remove(int index) {
    //非法参数校验
    rangeCheck(index);

    //更新modCount
    modCount++;
    //获取指定的数据
    E oldValue = elementData(index);

    //计算要移动数据的个数
    int numMoved = size - index - 1;

    //根据情况去移动数据
    if (numMoved > 0)
        //从index+1开始,所有数据向前移动一位,这样,index位置上的数据就被覆盖了,相当于移除了
        //这里可以看出,移除操作的效率不高
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //容器的最后一位置空,更新size
    elementData[--size] = null; 

    return oldValue;
}

说明:
1. 移除操作实际上是一个覆盖数据的操作,是将后面一个数据覆盖到前一个位置,如果是末尾的数据,则直接置空。
2. 与add()中的耗时操作类似:可能移动大量的数据导致低效率

移除特定的数据:remove(Object o)

//移除容器中第一个相应的数据
public boolean remove(Object o) {
    //首先对o进行空判断,防止o.equal()报错
    //两部操作的逻辑一致

    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
    modCount++;
    //计算要移动数据的个数
    int numMoved = size - index - 1;
    //根据情况去移动数据
    if (numMoved > 0)
        //从index+1开始,所有数据向前移动一位,这样,index位置上的数据就被覆盖了,相当于移除了
        //这里可以看出,移除操作的效率不高
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //容器的最后一位置空,更新size
    elementData[--size] = null; 
}

说明:与remove(int index)相比,多了一步遍历查询的操作

indexOf()的分析

//根据Object找到第一个出现的位置
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;
    }
    //没有找,返回-1
    return -1;
}

这里代码与LinkedList中的类似,都是遍历对比。效率高也仅仅是这里锁定了内存。

结语

了解了ArrayList的原理,对我们优化代码,提升效率会有很大的帮助,这里也给出使用建议:
如果查询操作较多,使用ArrayList的效果更好.

LinkedList源码理解放在一起阅读,效果更好
转载请标明出处http://blog.youkuaiyun.com/qq_26411333/article/details/51583376#t10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值