JDK8系列:List集合 之 ArrayList 源码分析

1、ArrayList 简介

1)ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类

2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示它们所封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加

           如果想ArrayList中添加大量元素,可使用ensureCapacity方法一次性增加capacity,可以减少增加重分配的次数提高性能

3)ArrayList的用法和Vector向类似,但是Vector是一个较老的集合,具有很多缺点,不建议使用

          另外,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的

 

2、ArrayList 成员变量

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, 
                                                                                java.io.Serializable{
    private static final long serialVersionUID = 8683452581122892189L;

    // 默认容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 空数组,用于空实例
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 当前数据对象存放地方,当前对象不参与序列化
    transient Object[] elementData;
    
    // 当前数组长度
    private int size;

    // 数组最大长度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

 

之分析了一部分方法,其他不重要的就不做分析了!!!

3、构造方法

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {  //如果初始化容量为0,则用特殊的空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

// 此时我们创建的ArrayList对象中的elementData中的长度是1,size是0
// 当进行第一次add的时候,elementData将会变成默认的长度:10.
public ArrayList() {
    // 没容量,则用默认的空数组,但实际上是10.后面的calculateCapacity有体现
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

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;
    }
}

 

重点掌握:

  1. 初始空时,调用 add() 方法时是怎么扩容的
  2. 正常情况下,调用 add() 是怎么扩容的
  3. 删除中间的元素是怎么完成的?将index后面的元素向前移动1步

System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length):

src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。

 

4、核心方法

4.1、add方法(4个)

(1)add(E e)

public boolean add(E e) {

    // 确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,
    // 先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    
    // 在数据中正确的位置上放上元素e,并且size++
    elementData[size++] = e;
    return true;
}

 ensureCapacityInternal(int minCapacity) 

确定容量,不够则扩容,包含了modCount++

如果初始时为空,则返回的是 DEFAULT_CAPACITY=10,而不是 minCapacity = size+1=1

// 确定内部容量的方法
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

calculateCapacity(Object[] elementData, int minCapacity) 

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 判断初始化的elementData是不是空的数组,也就是没有长度
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);  //返回两者间的较大者
    }
    return minCapacity;
}

ensureExplicitCapacity(int minCapacity) 

// 确认实际的容量,这个方法就是真正的判断elementData是否够用
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;  //修改次数+1

    // 预计元素添加之后,超过容量
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

 grow(int minCapacity)     扩容

newCapacity = max(minCapacity, 3/2 * oldCapacity)

如果 minCapacity > MAX_ARRAY_SIZE,则  minCapacity = Integer.MAX_VALUE

// 数组扩容
private void grow(int minCapacity) {

    int oldCapacity = elementData.length;
    // 新容量等于 = 3/2 * 就容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);

    // 新容量取 newCapacity和minCapacity中的较大者
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;

    // 如果新容量大于最大容量,则 newCapacity取Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // copyOf   第1个参数:旧数组    第2个参数:新数组的大小    默认第三个参数是就数组的大小
    // 就是将就数组的内容复制到一个大小为newCapacity的新数组中,并将新数组赋给elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}

hugeCapacity(int minCapacity) 

// 取Integer.MAX_VALUE和MAX_ARRAY_SIZE中的较大者
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) /
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

(2)add(int index, E element) 

// 在特定位置添加元素,也就是插入元素
public void add(int index, E element) {

    // 检查index也就是插入的位置是否合理
    rangeCheckForAdd(index);
    // 判断是否数组.length是否够用
    ensureCapacityInternal(size + 1);  // Increments modCount!!

    // 这个方法就是用来在插入元素之后,要将index之后的元素都往后移一位
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    // 在目标位置上存放元素
    elementData[index] = element;
    // size增加1
    size++;
}

rangeCheckForAdd(int index) 

检查 index 是否在 数组的下表范围内

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

总结

正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。

当我们调用add方法时,实际上的函数调用如下:

说明:初始化lists大小为0,调用的ArrayList()型构造函数,那么在调用lists.add(8)方法时,会经过怎样的步骤呢?下图给出了该程序执行过程和最初与最后的elementData的大小。

说明:我们可以看到,在add方法之前开始elementData = {};调用add方法时会继续调用,直至grow时参数minCapacity=10,oldCapacity=0,则newCapacity = minCapacity‘最后elementData的大小变为10,之后再返回到add函数,把8放在elementData[0]中。

(3)addAll(Collection<? extends E> c)

将集合c中元素输出到数组,利用 arraycopy() 将旧数组数据复制到新数组中

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();  //将集合c中的元素复制到数组a中
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 确定新容量(包含modCount++,扩容等)
    
    // 将旧数组中的元素复制到新数组 
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

(4)addAll(int index, Collection<? extends E> c) 

和上面基本一样

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);  //检查index是否符合要求

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 确定新容量,修改modCount

    int numMoved = size - index;
    // 将集合c中复制到list指定位置index处
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

 

4.2、删除方法

比添加元素add简单,因为不会关系到数组扩容

实现主要是:快慢指针 和 System.arraycopy()

(1)remove(int index)

删除指定index处的元素

public E remove(int index) {
    rangeCheck(index);  //检查index是否符合要求

    modCount++;   // 修改次数+1
    E oldValue = elementData(index);  // index处的元素值

    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 将index后的元素前移1步
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // 将原来结尾处置空,方便GC

    return oldValue;
}

(2)remove(Object o) 

删除list中的元素o,实际的删除动作发生在 fastRemove(index):将index后面的元素向前移到1步。和上面的remove(int)原理基本一样,只是多了一个确定o的index

public boolean remove(Object o) {
    // 如果o==null,也要删除对应的null
    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;
}

fastRemove(int index) 

private void fastRemove(int index) {

    // 修改次数+1
    modCount++;  
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 将index后面的元素向前移到1步
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

(3)removeAll(Collection<?> c)

实际上是 调用的批量移除:batchRemove

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);  // 检查c非空
    return batchRemove(c, false);  //批量移除
}

 batchRemove(Collection<?> c, boolean complement)

批量移除

complement = true时,表示将集合c中的元素保留,将list俩民不在集合c中的元素删除

complement = false时,表示将集合c中的元素删除

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    // r表示工作指针(快指针)
    // w表示最新的保留元素的指针(慢指针)
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            // 如果元素满足条件(不在集合c,或者在集合c)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // 说明前面出现了异常,为了保证r后面的元素不受影响。将r到size处的元素复制到w后面。
        if (r != size) {
            // 将elementData中的从r开始的长度为size-r的元素 复制到 elementData从w开始处
            System.arraycopy(elementData, r, elementData, w, size - r);
            w += size - r;  //更新w的位置,w现在所处位置就是结果的位置
            // 最后的结果是0~w+(size-r),0~w是正常处理的结果,w~w+(size-r)是还未处理的部分
        }

        // 说明,list至少有一个元素满足条件要删除(满指针没到达底部),那么需要清理被删除的部分
        // 注意到这里,及格过了上面的if之后,0~w是需要保存的结果,而w~size是需要置空的部分
        if (w != size) {
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;  //更新modCount
            size = w;              //更新size
            modified = true;       //被修改过
        }
    }
    return modified;
}

(4)removeIf(Predicate<? super E> filter) 

Bitset中主要存储的是二进制位,做的也都是位运算,每一位只用来存储0,1值,主要用于对数据的标记。Bitset的基本原理是,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用的时候可以根据某一个位是否为0表示此数是否出现过。JDK中的BitSet集合对是布隆过滤器中经常使用的数据结构Bitmap的相对简单的实现。BitSet采用了Bitmap的算法思想。

Java的BitSet使用一个Long(一共64位)的数组中的没一位(bit)是否为1来表示当前Index的数存在不 

 实现要点:

(1)通过快慢指针进行移除,之后将后面的位置置空

(2)通过BitSet来讲需要移除的位置标注,如果其他线程在此期间修改了list,则可以保证集合没有修改。(因为此阶段只是标注了,还未修改list)

public boolean removeIf(Predicate<? super E> filter) {
    // filter非空
    Objects.requireNonNull(filter);

    // 找出要删除的元素
    // 在此阶段从筛选器引发的任何异常都将使集合保持不变

    int removeCount = 0;  //记录需要移除的元素个数
    final BitSet removeSet = new BitSet(size);
    final int expectedModCount = modCount;  //保存当前的modCount
    final int size = this.size;

    // 如果modCount != expectedModCount,说明有其他线程修改了list
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        if (filter.test(element)) {  //元素满足过滤器条件
            removeSet.set(i);  //BitSet将对应index设置为1,表示之后需要删除
            removeCount++;     // 需要移除的元素的个数
        }
    }

    // 如果统计期间其他线程修改了list,则抛出异常
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

    // 到这里,说明上面没有发生异常,统计正常!!!
    // 将剩余的元素移到移除元素留下的空间上
    final boolean anyToRemove = removeCount > 0;
    // 如果有需要移除的元素
    if (anyToRemove) {
        final int newSize = size - removeCount;  //更新size

        // i是快指针,j是满指针
        for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
            // 根据bit位来判断是否移除
            i = removeSet.nextClearBit(i);
            elementData[j] = elementData[i];
        }

        // 将余下的位置置空
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;  //更新size

        // 如果次期间有其他线程修改list,则抛出异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        modCount++;  //修改次数+1
    }

    return anyToRemove;
}

(5)removeRange(int fromIndex, int toIndex) 

讲 [fromIndex, toIndex] 范围内的元素移除

protected void removeRange(int fromIndex, int toIndex) {
    modCount++;   //修改次数+1
    int numMoved = size - toIndex;   //需要移除的元素个数
    // 通过arraycopy()进行移除
    System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);

    // 讲余下部分置空
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

(6)retainAll(Collection<?> c) 

讲不在集合c中的元素删除,和前面的方法基本一样。只是调用 batchRemove()时的参数不一样

public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, true);
}

 

5、其他方法(只讲一部分)

像 clear() 、isEmpty()、iterator() 、listIterator()  、size()、toArray()等简单的方法就不说了

(1)contains(Object o) 

实际上就是调用了 indexOf(object o)

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

(2)get(int index) 

实际上就是调用了 elementData(int)

public E get(int index) {
    rangeCheck(index);  // 检查index的合法性
    return elementData(index);
}

elementData(int index) 

E elementData(int index) {
    return (E) elementData[index];
}

(3)set(int index, E element) 

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

 (4)lastIndexOf(Object o) 

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;
}

(5)replaceAll(UnaryOperator<E> operator)

public void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final int expectedModCount = modCount;
    final int size = this.size;
    
    // 就是一个一个替换
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        elementData[i] = operator.apply((E) elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

(6)sort(Comparator<? super E> c)

实际上是调用的 Arrays.sort()

public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

(7)trimToSize() 

将此ArrayList实例的容量 修剪 为列表的当前大小。应用程序可以使用此操作最小化ArrayList实例的存储。

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0) ? 
                            EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
    }
}

 

5、内部类

不做讲解

 

6、总结

1)arrayList可以存放null
2)arrayList本质上就是一个elementData数组
3)arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法
4)arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素。
5)arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果
6)arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值