深入解析ArrayList

1. ArrayList的底层数据结构

List接口的可调整大小的数组实现。

transient Object[] elementData;

特点:

  • 增删慢:每次增删非末尾元素,都要调整数组大小,拷贝及移动元素位置。
  • 查询快:数组在内存中是一块连续的空间,因此可以根据地址+索引的方式快速获取对应位置的元素。

2.ArrayList源码分析

2.1 属性介绍

属性名类型描述
DEFAULT_CAPACITYint初始化容量,默认为10
EMPTY_ELEMENTDATAObject[]共享空数组实例
DEFAULTCAPACITY_EMPTY_ELEMENTDATAObject[]将它与EMPTY_ELEMENTDATA区分开来,以便知道在添加第一个元素时应该填充多少。
sizeintArrayList数据个数

2.2构造方法

在这里插入图片描述

public ArrayList(Collection<? extends E> c) {
    	//将集合转换为数组
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //如果elementData不是Object[]的话,就重新拷贝成Object[]
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果集合长度为0,就替换为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

2.3 添加方法

方法名描述
public boolean add(E e)将指定的元素追加到此列表的末尾。
public void add(int index, E element)在此列表中的指定位置插入指定的元素。
public boolean addAll(Collection<? extends E> c)按指定集合的Iterator返回的顺序,将指定集合中的所有元素追加到此列表的末尾。
public boolean addAll(int index, Collection<? extends E> c)从指定的位置开始, 将指定集合中的所有元素插入到此列表中。
2.3.1 add(E e)
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保内部容量比元数组长度大
    elementData[size++] = e;
    return true;
}


private void ensureCapacityInternal(int minCapacity) {
    //确保最小容量不会小于10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

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;
    //新的容量为原数组长度的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);
}
2.3.2 add(int index, E element)
public void add(int index, E element) {
    //如果index>size或index<0,则抛出IndexOutOfBoundsException
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);
    //将index后的数据整体后移一位并拷贝到原数组中
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
2.3.3 addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
    //将集合c转换为数组a
    Object[] a = c.toArray();
    //计算数组a的长度
    int numNew = a.length;
    //校验以及扩容
    ensureCapacityInternal(size + numNew);  // Increments modCount
    //将数组a拷贝到list后面
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
2.3.4 addAll(int index, Collection<? extends E> 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后的元素整体后移numNew位
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);
	//将数组a拷贝到list集合的index后
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

2.4 删除方法

方法名描述
public E remove(int index)删除指定位置的元素
public boolean remove(Object o)如果存在,则删除第一个出现的此元素
protected void removeRange(int fromIndex, int toIndex)从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
public boolean removeAll(Collection<?> c)从此列表中删除指定集合中包含的所有元素
2.4.1 remove(int index)
public E remove(int index) {
    //索引校验
    rangeCheck(index);

    modCount++;
    //得到该位置的值
    E oldValue = elementData(index);
	//计算机集合需要移动元素的个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //将index后的元素整体往前移一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将最后一个元素置为null,便于垃圾回收
    elementData[--size] = null; // clear to let GC do its work
	//返回被删除的元素
    return oldValue;
}
2.4.2 remove(Object o)
public boolean remove(Object o) {
    if (o == null) {
        //遍历集合
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //删除第一个null
                fastRemove(index);
                return true;
            }
    } else {
        //遍历集合
        for (int index = 0; index < size; index++)
            //当找到第一个与o相同的元素时,就删除
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    //即没有找到该元素
    return false;
}

2.5修改方法

  • public E set(int index, E element) 根据索引修改集合元素
public E set(int index, E element) {
    rangeCheck(index);

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

2.6 获取方法

  • public E get(int index) 根据索引获取元素
public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

3.常见面试题

3.1 ArrayList频繁扩容导致添加性能急剧下降,如何处理?

  • 案例
public class Test01 {
    public static void main(String[] args) {
        //创建集合对象
        List<String> list = new ArrayList<String>();
        //添加元素
        list.add("hello");
        list.add("PHP");
        list.add("Java");
        long startTime = System.currentTimeMillis();
        //需求:还需要添加10W条数据
        for (int i = 0; i < 100000; i++) {
            list.add(i+"");
        } 
        long endTime = System.currentTimeMillis();
        System.out.println("未指定容量: "+ (endTime - startTime));
    }
}

结果
在这里插入图片描述
扩容次数太多导致性能低下。

  • 解决方案
public class Test01 {
    public static void main(String[] args) {
        //创建集合的时候指定足够大的容量
        List<String> list = new ArrayList<String>(100000);
        //添加元素
        list.add("hello");
        list.add("PHP");
        list.add("Java");

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
        	list.add(i+"");
        }
        long endTime = System.currentTimeMillis();
        System.out.println("指定容量: "+ (endTime - startTime));
    }
}

结果
在这里插入图片描述
**注意:**这种优化方式只针对特定的场景,如果添加的元素是少量的、未知的,不推荐使用

3.2 如何复制某个ArrayList到另一个ArrayList中去?

  1. 使用clone()方法,比如ArrayList newArray = oldArray.clone();
  2. 使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject);
  3. 使用Collection的copy方法。

3.3 ArrayList 和 LinkList区别?

参考链接

3.4 什么情况下你会使用ArrayList?什么时候你会选择LinkedList?

多数情况下,当你遇到访问元素比插入或者是删除元素更加频繁的时候,你应该使用ArrayList。另外一方面,当你在某个特别的索引中,插入或者是删除元素更加频繁,或者你压根就不需要访问元素的时候,你会选择LinkedList。这里的主要原因是,在ArrayList中访问元素的最糟糕的时间复杂度是”1″,而在LinkedList中可能就是”n”了。在ArrayList中增加或者删除某个元素,通常会调用System.arraycopy方法,这是一种极为消耗资源的操作,因此,在频繁的插入或者是删除元素的情况下,LinkedList的性能会更加好一点。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值