ArrayList详解

前面介绍Java集合三大热门之二——HashMap和ConcurrentHashMap,那么剩下的一个就是ArrayList了,在使用列表时,除非特殊情况,一般都会用ArrayList。

一、简介

List允许有重复元素、且存储有序,实现主要有ArrayList、CopyOnWriteArrayList、Vector和LinkedList:

  • ArrayList:底层是数组,查询快,增删慢,线程不安全。

  • CopyOnWriteArrayList:底层是数组,读写分离,线程安全,效率较Vector高效,不过内存消耗较大。(add和remove等操作都加了ReentrantLock锁,当写的时候COW会先从原数组拷贝一份,然后在新的数组上做些操作,写完之后再将原数组引用指向新数组,读操作没有锁)

  • Vector:底层是数组,线程安全,但效率低,扩容时为原来的2倍。(所有方法都加了Synchronized锁)

  • LinkedList:底层是链表,查询慢,增删快,线程不安全。

二、源码解析

2.1 基础属性

//默认初始容量为10
private static final int DEFAULT_CAPACITY = 10;
//空实例数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认的空实例数组,在第一次调用ensureCapacityInternal时会初始化长度为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放元素的数组
transient Object[] elementData; // non-private to simplify nested class access
//数组当前元素的数量
private int size;
//数组允许的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.2 构造方法

//指定初始容量的有参构造
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);
    }
}
​
//使用默认容量的无参构造
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

2.3 替换指定位置元素

//用指定元素替换指定位置的元素
public E set(int index, E element) {
    //检验是否数组越界
    rangeCheck(index);
    //根据index获取原元素
    E oldValue = elementData(index);
    //替换为新元素
    elementData[index] = element;
    //返回原元素
    return oldValue;
}
//ps:下方多处用到
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2.4删除元素

2.4.1 根据索引删除

//删除指定索引位置的元素
public E remove(int index) {
    //检查是否越界
    rangeCheck(index);
    modCount++;
    //获取要被删除的元素
    E oldValue = elementData(index);
    //计算需要移动的元素个数,
    int numMoved = size - index - 1;
    //判断是否需要移动。解释:如果size为5,index为4,那么5-4-1=0,因为index=4刚好是最后一个元素,无需移动。
    if (numMoved > 0){
        //将index+1之后的元素向左移动一位
        System.arraycopy(elementData, index+1, elementData, index,
                numMoved);
    }
    //将元素个数-1,并将size-1位置置空(因为上面将元素左移了,所以size-1位置的元素是重复的;或者本身就是尾元素,直接置空)
    elementData[--size] = null; // clear to let GC do its work
    //返回删除元素的值
    return oldValue;
}

2.4.2 根据元素值删除

//删除与指定值相同的元素,如果匹配到删除第一个与之匹配的元素,返回true,反之返回false
public boolean remove(Object o) {
    if (o == null) {
        //入参为null,遍历数组找到第一个为null的元素
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //调用fastRemove将改元素移除
                fastRemove(index);
                return true;
            }
    } else {
        //同理,遍历数据找到第一个值为o的元素
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
​
//删除指定index位置的元素
private void fastRemove(int index) {
    modCount++;
    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
}

2.5 添加元素

2.5.1 添加到数组尾部

//在数组尾部增加一个元素
public boolean add(E e) {
    //将modCount+1,并校验添加元素后是否需要扩容,确保能存下size+1个元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //在数组尾部添加元素,并将size+1
    elementData[size++] = e;
    return true;
}

2.5.2 添加到指定位置

public void add(int index, E element) {
    //检查是否越界
    rangeCheckForAdd(index);
    //将modCount+1,并校验添加元素后是否需要扩容,确保能存下size+1个元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将index之后的元素向右移动1位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //index位置设置为要添加的元素
    elementData[index] = element;
    //元素个数+1
    size++;
}
​
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

举个例子:

2.6 扩容

2.5小节在add方法添加元素之前会先调用ensureCapacityInternal方法,该方法有两个作用,一个是如果没初始化就先初始化,二是检验添加元素后是否需要扩容:

private void ensureCapacityInternal(int minCapacity) {
    //判断ArrayList是否是无参构造创建的
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //是无参构造,主要是给DEFAULTCAPACITY_EMPTY_ELEMENTDATA设置初始容量
        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) {
    // overflow-conscious code
    //原来的容量
    int oldCapacity = elementData.length;
    //新容量=老容量+老容量/2,也就是原来的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
​
    if (newCapacity - minCapacity < 0){
        //说明新容量比需要的最小容量还小,把最小容量作为新容量
        newCapacity = minCapacity;
    }
​
    if (newCapacity - MAX_ARRAY_SIZE > 0){
        //说明超过数组最大大小阈值,调用hugeCapacity方法设置一个合适的容量
        newCapacity = hugeCapacity(minCapacity);
    }
​
    // minCapacity is usually close to size, so this is a win:
    //将原数组元素拷贝到容量为newCapacity的新数组
    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;
}

三、如何实现序列化

大家可能没注意到,elementData数组使用transizent关键字修饰的,这就意味着序列化ArrayList时会忽略elementData,反序列化时会给其分配默认值,这肯定不行,那么它是如何实现序列化和反序列化的呢?

//存放元素的数组
transient Object[] elementData; // non-private to simplify nested class access

这有个知识点。

ps:在序列化过程中,如果序列化类中定义了writeObject和readObject方法,JVM会调用对象类里的writeObject和readObject方法完成序列化和反序列化。如果没有这俩方法,会默认调用ObjectOutputStream的defaultWriteObject方法和ObjectInputStream的defaultReadObject方法。

所以ArrayList重写了writeObject和readObject方法来完成对elementData的序列化:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();
    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);
    // Write out all elements in the proper order.
    //注意:是size
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
​
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;
    // Read in size, and any hidden stuff
    s.defaultReadObject();
    // Read in capacity
    s.readInt(); // ignored
    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        int capacity = calculateCapacity(elementData, size);
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
        ensureCapacityInternal(size);
        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

为什么要用transient?

ArrayList实际上是动态数组,每次放满后自动扩容,假如10个容量满了,这是又来一个元素要add,那么这时候就会扩容为15容量,但实际是11个元素,那么就会有4个null(容量越大,null值越多),而null值是没有意义的(除了add的null)

使用Java自带的序列化机制会把没有意义的null值也序列化了,会造成空间浪费,所以ArrayList把元素数组设置为transient,并自己重写writeObject和readObject来实现序列化操作。

End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值