Java容器(二):List 之 ArrayList源码分析

Java容器

集合框架

二、List

List:可重复、有序(存取顺序相同)

List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。

2.1 ArrayList

2.1.1 类结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
  • ArrayList 继承了 AbstractList,实现了List接口。
  • ArrayList 实现了 RandomAccess接口,表明具有快速随机访问功能。RandomAccess 接口只是标识,并非实现了 RandomAccess 接口才具有快速随机访问功能。
  • ArrayList 实现了 Cloneable接口,即实现克隆功能。
  • ArrayList 实现了 Serializable接口,表示支持序列化。

2.1.2 属性

// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//共享的空数组实例,用于默认大小的空实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList元素存储到的数组缓冲区。
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList包含的元素数量
private int size;

2.1.3 构造方法

ArrayList 的底层实现了一个数组,这个数组类型是泛型,表示什么元素类型都可以存放。

ArrayList 的构造方法有以下三种:

  • List<E> list1 = new ArrayList<E>(int n);

    携带数字参数,表示初始化容量为n的空列表;如果 n == 0,则初始化一个空数组;如果小于0,则抛出异常。

    //构造一个具有指定初始容量的空列表。
    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);
        }
    }
    
  • List<E> list2 = new ArrayList<E>();

    不携带参数,表示初始化容量为10的空列表

    //构造一个初始容量为 DEFAULT_CAPACITY(默认为10) 的空列表
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
  • List<E> list3 = new ArrayList<E>(Collection<> list);

    初始化包含一个集合的列表

    //构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
    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;
        }
    }
    

2.1.4 常用方法

  1. 增加元素
  • add(E e);:添加一个元素到列表的末尾

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    • e 是传入的参数,elementData 是当前的数组,size 是当前数组的长度

    • 数组进行扩容,增加一个空间

    • 扩容完成继续执行,将元素 e 存放在第 size+1 个位置

    • 存放完成后数组长度+1

  • add( int index, E element );:在指定的位置添加元素

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    
    • index 是指定位置,element 是添加的元素
    • rangeCheckForAdd 判断index是否超出数组范围,超出则抛出异常
    • 对数组进行扩容,增加一个空间
    • 扩容完成进行元素的添加,使用了系统本地方法进行数组的移动
    • 存放完成后数组长度+1
  1. 删除元素
  • remove(int index);:删除列表中指定位置的元素

    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);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }
    
    • index 是指定位置
    • rangeCheck 判断index是否超出数组范围,超出则抛出异常
    • 取出下标对应的值,计算将要移动的数量
    • 如果移动的数量大于0,则使用了系统本地方法进行数组的移动
    • 存放完成后数组长度-1,并将最后一个空间设置为空,返回旧值
  • remove(Object o);:删除列表中第一个出现o的元素

    public boolean remove(Object o) {
        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;
    }
    
    • 如果 o 为空,则遍历数组,找到第一个空元素的下标
    • 否则,遍历数组,找到第一个为 o 的下标
    • 没有找到则返回 false
    • 如果找到,则执行 fastRemove 方法,将该位置的元素删除,并使用本地方法移动数组
  • clear();:清除所有的元素,没有返回类型

    public void clear() {
        modCount++;
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }
    
    • 遍历整个数组,将每个元素置为空
  1. 更新元素

    set(int index, E element):用element替换index位置上的元素

    public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    • 调用 Object 的检查下标方法,判断下标是否小于0或是否大于数组长度
    • 直接将 index 下标的元素转化为传进来的下标的元素
    • 最后返回原来的元素
  2. 查询元素

  • get(int index):返回指定索引的元素

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    
    • 判断下标是否有效,有效则直接返回数组的第 index 个元素
  • indexOf(Object o):返回此列表中指定元素的第一次出现的索引,如果列表不包含此元素,返回-1

    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;
        }
        return -1;
    }
    
    • 如果 o 为空,则遍历数组,找到第一个空元素的下标
    • 否则,遍历数组,找到第一个为 o 的下标
    • 没有找到则返回 -1
  • lastIndexOf(Object o):返回此列表中指定元素的最后一次出现的索引,如果列表不包含此元素,返回-1

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    
    • lastIndexOf 则与 indexOf 相反,从尾到头找元素 o 的位置,最终返回最后一次出现 o 的位置
  1. 其他操作
    • size():返回此列表的元素数
    • sort(Comparator c):按照指定的排序规则排序
    • subList( int from , int to ):返回从from到to之间的列表
    • toArray():将列表转化为数组

2.1.5 扩容机制

  1. 通过 ensureCapacity(int minCapacity) 方法判断扩容量

    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0
            : DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    
    • 如果当前数组不是默认大小的空实例,则最小扩容量为0
    • 如果当前数组是默认大小的空实例,则最小扩容量为默认大小10
    • 如果要求扩容量 大于 最小扩容量,则调用ensureExplicitCapacity(minCapacity)
  2. 通过 ensureExplicitCapacity(int minCapacity) 方法判断是否需要扩容

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    列表所需的最小长度大于当前列表的长度,则调用 grow(minCapacity) 方法

  3. 通过 grow(int minCapacity) 方法,结合hugeCapacity(int minCapacity)方法,设置当前数组

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
    • 定义新列表的长度为原列表的1.5倍( (oldCapacity >> 1) 为右移运算,右移一位相当于除以2)

    • 如果新数组的长度依旧小于列表所需的长度

      • 如果原列表为空,则返回 DEFAULT_CAPACITY 和列表所需长度的最大值,DEFAULT_CAPACITY 默认为10
      • 如果新列表的长度小于0,则报 OOM 异常(内存溢出异常,将会在JVM中学习)
      • 否则返回列表所需长度
    • 判断新数组是否比 MAX_ARRAY_SIZE 小(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)

      • 是,返回新数组长度
      • 否,进行 hugeCapacity 判断,返回 MAX_ARRAY_SIZE 或是 Integer.MAX_VALUE
    • 扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。

2.1.6 Fail-Fast

protected transient int modCount = 0;
  • java.util.ArrayList 不是线程安全的,

  • modCount 用来记录 ArrayList 结构发生变化的次数。

  • 结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。

  • 在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出ConcurrentModificationException。

  • 在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 ArrayList。

  • 遍历那些非线程安全的数据结构时,尽量使用迭代器

2.1.7 线程安全

ArrayList是线程不安全的。在其迭代器iteator中,如果有多线程操作导致modcount改变,会执行fast-fail。抛出异常。

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

2.1.8 优缺点

优点:

  1. ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快
  2. ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已

缺点:

  1. 删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能
  2. 插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

因此,ArrayList比较适合顺序添加、随机访问的场景。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值