java源码阅读——ArrayList


ArrayList类是一个我们经常要使用到的容器,它其实就是一个大小可以自动调整的数组

还是先来看一下ArrayList继承的类和实现的接口
在这里插入图片描述

Iterable

实现了这个接口的对象允许使用增强 for 语句来进行迭代

Collection

在这里插入图片描述

collection是java容器实现的根接口,collection代表了一组对象(object),collection中的对象称为该collection的元素(elements),有些collection允许重复的元素出现,有一些不允许,有些collection的元素是有序的,而有些则是无序的,这类需求在更加具体的子接口中去实现(例如图中的List和Set)

collection中最主要的方法如下图所示,常用的也就是这些个方法
在这里插入图片描述
collection常用方法介绍

方法名介绍
size()返回collection中元素的个数
isEmpty()判断该collection是否为空
contains(Object)判断collection中是否包含某个对象
add(E)将某个元素加入到collection中
remove(Object)删除collection中存在的某个对象
containsAll(Collection<?>)判断collection中是否包含传入参数中所有的元素
addAll(Collection<? extends E>)将传入参数中的所有元素加入到collection中
removeAll(Collection<?>)将传入参数中所有存在的元素删除
retainAll(Collection<?>)和removeAll相反,将传入参数中所有存在的元素保留,也就是说删除传入参数中不存在的元素
clear()清除collection中所有元素

List

如前所述,List是一个实现有序集(也叫序列)的接口,用户可以控制元素插入的位置,可以通过元素再有序集中的索引访问某个元素,和Set不同的是,List中的元素是可以重复的。

在List接口中,我们可以看到有关索引的操作,例如获取/设置元素的get、set,获取对象索引的indexOf、lastIndexOf,以及在add和remove中也允许指定索引
在这里插入图片描述

ArrayList

终于到了ArrayList这里啦,ArrayList继承自AbstractList这个抽象类,而AbstractList又继承自AbstractCollection,这两个抽象类实际上分别是List和Collection的骨架实现,部分实现了接口的一些方法,以便于减少后面的代码工作量。

先来看看定义了啥属性

private static final int DEFAULT_CAPACITY = 10; // 默认容量

private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组的共享实例

transient Object[] elementData; // 用于存储数据的缓冲区,ArrayList的长度就是这个elementData的长度

构造函数

无参构造函数

如果调用无参构造函数,那么默认会构造一个空数组

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

传入容量大小初始化

如果传入的容量c大小大于0,那么就会构建一个长度为c的对象数据,如果c为0,那么仍然会构建一个空数组,小于0则抛出异常

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {	// 如果传入的容量c大小大于0,那么就会构建一个长度为c的对象数据
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {	// 如果容量为0,那么仍然会构建一个空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}

传入collection初始化

如果传入的collection是ArrayList的实例,那么直接赋值,如果不是,那么将里面的元素转换为Object对象再进行赋值

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    // 改变size大小,并判断是否为0
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // 如果size为0,则用空数组赋值
        elementData = EMPTY_ELEMENTDATA;
    }
}

添加元素和扩容

添加元素使用的是add方法,jdk11这里的实现和jkd8还是有所区别的,add方法会向某个索引的前一个位置添加元素,

// 不填写位置的add方法默认在最后一个位置添加
public boolean add(E e) {
 	modCount++;	// ArrayList每次发生修改,modCount计数都会++
    add(e, elementData, size);
    return true;
}
// e是待添加的元素,s是索引,
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)	// 如果要添加的位置等于缓存数组的长度,也就是当前缓存数组已经满了
        elementData = grow();	// 就会调用grow方法进行扩容
    elementData[s] = e;
    size = s + 1;
}

ArrayList的扩容机制

private Object[] grow() {
    return grow(size + 1);
}
// 传入一个minCapacity代表最低的容量要求,表示扩容后数组大小至少要超过minCapacity
private Object[] grow(int minCapacity) {
	// 将原来的元素复制到新建的更大容量的数组
    return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
// 返回一个数,代表扩容后缓冲数组的应该具有长度,要求至少大于等于minCapacity,不过不能超过设定的上限
private int newCapacity(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);	// 从这句话可以看出,扩容一次实际上就是变为原有的1.5倍
    if (newCapacity - minCapacity <= 0) {	// 如果扩容后的大小仍然小于等于minCapcity的值
    	// 这两个if都算是错误处理
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // 传入参数错误
            throw new OutOfMemoryError();
        // 这种情况下应该返回minCapacity
        return minCapacity;
    }
    // 这里大致意思是,扩容后数组的大小不能超过Integer.MAX_VALUE这个上限,如果超过了,那么数组的大小将会变为Integer.MAX_VALUE
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

删除元素

// 删除某个索引的元素,返回被删除的那个值
public E remove(int index) {
	// 先检查索引是否合法
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}
// 快速删除,没有检查索引的操作
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    // 修改size大小
    if ((newSize = size - 1) > i)
    	// 将删除位置后面的元素均向前挪动一格
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 将数组最后一个有元素的位置置为null(因为前面只是把元素向前挪了一格,所以最后一格的元素还在,要手动删除)
    es[size = newSize] = null;
}

迭代操作

容器的迭代操作由私有类Itr实现

private class Itr implements Iterator<E> {
    int cursor;       // 下一个元素的索引
    int lastRet = -1; // 最后一个元素的索引,-1代表没有最后一个元素
    int expectedModCount = modCount;	// 
	// 如果下一个元素的索引不等于元素的个数,那么就说明可以继续迭代下去
	public boolean hasNext() {
        return cursor != size;
    }

	public E next() {
        checkForComodification();	// 检查迭代过程中是否发生修改
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;	// 索引值加1
        return (E) elementData[lastRet = i];
    }
	// 检查迭代过程中是否发生修改,如果发生了修改,那么就抛出异常,判别的依据是modCount是否和原来的值相等,因为ArrayList里面每一步的修改操作均会修改modCount,所以一旦这个值和原来的不同就说明数组发生了变化
	final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

线程同步

从上面的代码实现方式中我们可以知道,ArrayList的实现是不同步的,如果多个线程访问ArrayList并且对其进行修改的话,那么需要对其进行手动的同步操作

如果想要使用线程同步的List,那么可以使用synchronizedList包装ArrayList

List list = Collections.synchronizedList(new ArrayList(...));
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值