Java集合详述

164 阅读9分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

1、集合

1.1 JAVA集合关系图

1.2 ArrayList源码分析

1、简介

ArrayList 是 java 集合框架中比较常用的数据结构了。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccess、Cloneable、Serializable 接口,所以ArrayList 是支持快速访问、复制、序列化的。

2、重要属性

默认初始容量 10

1/**
2 * Default initial capacity.
3 */
4private static final int DEFAULT_CAPACITY = 10;

EMPTY_ELEMENTDATA与DEFAULTCAPACITY_EMPTY_ELEMENTDATA这两个变量使用在构造函数中

这两个空的数组有什么区别?We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.区分第一次添加元素的时候知道elementData从空的构造函数还是有参构造函数初始化的。以便于确认如何扩容。

1/**
2 * Shared empty array instance used for empty instances.
3 */
4private static final Object[] EMPTY_ELEMENTDATA = {};
5
6/**
7 * Shared empty array instance used for default sized empty instances
8 */
9private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

存储数组列表元素的数组缓冲区

1/**
2 * The array buffer into which the elements of the ArrayList are stored.
3 * The capacity of the ArrayList is the length of this array buffer. Any
4 * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
5 * will be expanded to DEFAULT_CAPACITY when the first element is added.
6 */
7transient Object[] elementData; // non-private to simplify nested class access

数组中元素的大小,size是指elementData中实际有多少个元素,elementData.length为集合容量,表示集合当前最多可以容纳多少个元素

1/**
2 * The size of the ArrayList (the number of elements it contains).
3 */
4private int size;

modCount定义在父类AbstractList中。记录List的操作次数。主要使用在Iterator中,防止在迭代的过程中集合被修改

1/**
2 * The number of times this list has been <i>structurally modified</i>\
3 */
4protected transient int modCount = 0;

3、构造函数

3.1 无参构造函数

需要注意的是无参构造函数,给elementData赋值的是一个空的数组,在第一次添加元素的时候才把容量扩大为10,不要被注释给误导了。此处可见无参构造器是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData

1/**
2 * Constructs an empty list with an initial capacity of ten.
3 */
4public ArrayList() {
5    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
6}

3.2 构造一个初始容量为initialCapacity的ArrayList

当initialCapacity>0,初始化一个大小为initialCapacity的Object数组,赋值给elementData;当initialCapacity==0,将EMPTY_ELEMENTDATA赋值给elementData;否则,抛出异常。

 1/**
 2 * Constructs an empty list with the specified initial capacity.
 3 */
 4public ArrayList(int initialCapacity) {
 5    if (initialCapacity > 0) {
 6        this.elementData = new Object[initialCapacity];
 7    } else if (initialCapacity == 0) {
 8        this.elementData = EMPTY_ELEMENTDATA;
 9    } else {
10        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
11    }
12}

3.3 通过指定的Collection来初始化一个ArrayList

将Collection转为数组并且赋值给elementData,elementData.length赋值给size;如果size != 0,则判断elementData类型是否为Object[]类型,不是则做一次转换(注意此处其实是一个官方bug解决方案);如果size == 0则将EMPTY_ELEMENTDATA赋值给elementData,相当于new ArrayList(0)

 1public ArrayList(Collection<? extends E> c) {
 2    elementData = c.toArray();
 3    if ((size = elementData.length) != 0) {
 4        // c.toArray might (incorrectly) not return Object[] (see 6260652)
 5        if (elementData.getClass() != Object[].class)
 6            elementData = Arrays.copyOf(elementData, size, Object[].class);
 7    } else {
 8        // replace with empty array.
 9        this.elementData = EMPTY_ELEMENTDATA;
10    }
11}

4、核心方法

4.1 add 操作

ensureCapacityInternal();

每次添加元素到集合中都会先确认集合容量的大小;

1public boolean add(E e) {
2    ensureCapacityInternal(size + 1);  // Increments modCount!!
3    elementData[size++] = e;
4    return true;
5}

calculateCapacity();

判断elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA则取DEFAULT_CAPACITY和minCapacity的最大值,也就是10;此处验证了在第一次插入的时候才会给数组初始容量10;

 1private void ensureCapacityInternal(int minCapacity) {
 2    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 3}
 4
 5private static int calculateCapacity(Object[] elementData, int minCapacity) {
 6    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 7        return Math.max(DEFAULT_CAPACITY, minCapacity);
 8    }
 9    return minCapacity;
10}

modCount++;

记录操作次数;

minCapacity - elementData.length > 0;

数组长度不够时对数组进行扩容,执行grow()函数的逻辑

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

oldCapacity + (oldCapacity >> 1)

默认将数组的大小扩容到原来的1.5倍

newCapacity - minCapacity < 0

如果扩容后的容量不足,则将所需容量minCapacity赋值给newCapacity,扩容后数组大小就是申请的容量

newCapacity - MAX_ARRAY_SIZE > 0

如果扩容后的数组容量太大,超过了Integer.MAX_VALUE - 8的大小,则数组的大小为hugeCapacity(),(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;

 1private void grow(int minCapacity) {
 2    // overflow-conscious code
 3    int oldCapacity = elementData.length;
 4    int newCapacity = oldCapacity + (oldCapacity >> 1);
 5    if (newCapacity - minCapacity < 0)
 6        newCapacity = minCapacity;
 7    if (newCapacity - MAX_ARRAY_SIZE > 0)
 8        newCapacity = hugeCapacity(minCapacity);
 9    // minCapacity is usually close to size, so this is a win:
10    elementData = Arrays.copyOf(elementData, newCapacity);
11}
12
13private static int hugeCapacity(int minCapacity) {
14    if (minCapacity < 0// overflow
15        throw new OutOfMemoryError();
16    return (minCapacity > MAX_ARRAY_SIZE) ?
17        Integer.MAX_VALUE :
18    MAX_ARRAY_SIZE;
19}

其余add()方法大同小异

 1public void add(int index, E element) {
 2    rangeCheckForAdd(index);
 3    ensureCapacityInternal(size + 1);  // Increments modCount!!
 4    System.arraycopy(elementData, index, elementData, index + 1, size - index);
 5    elementData[index] = element;
 6    size++;
 7}
 8
 9public boolean addAll(Collection<? extends E> c) {
10    Object[] a = c.toArray();
11    int numNew = a.length;
12    ensureCapacityInternal(size + numNew);  // Increments modCount
13    System.arraycopy(a, 0, elementData, size, numNew);
14    size += numNew;
15    return numNew != 0;
16}
17
18public boolean addAll(int index, Collection<? extends E> c) {
19    rangeCheckForAdd(index);
20    Object[] a = c.toArray();
21    int numNew = a.length;
22    ensureCapacityInternal(size + numNew);  // Increments modCount
23    int numMoved = size - index;
24    if (numMoved > 0)
25        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
26    System.arraycopy(a, 0, elementData, index, numNew);
27    size += numNew;
28    return numNew != 0;
29}

4.2 remove 操作

rangeCheck(index);

检查index是否合法,当index >= size时,抛出IndexOutOfBoundsException异常;

modCount++;

记录集合的操作次数;

E oldValue = elementData(index);

取出移除元素,用于放回给方法调用者;

if (numMoved > 0)

判断当前删除的集合元素是否时数组的最后一个元素,如果不是最后一个元素,则调用System.arraycopy()方法做一次数组拷贝;如果移除的元素是最后一个元素或者在数组复制结束之后,将数组的最后一个元素置为null,等待GC垃圾回收;--size数组的大小减一;

 1public E remove(int index) {
 2    rangeCheck(index);
 3    modCount++;
 4
 5    E oldValue = elementData(index);
 6    int numMoved = size - index - 1;
 7    if (numMoved > 0) 
 8        System.arraycopy(elementData, index+1, elementData, index, numMoved);
 9    elementData[--size] = null; // clear to let GC do its work
10    return oldValue;
11}

4.3 get 操作

rangeCheck(index);

检查index是否合法,当index >= size时,抛出IndexOutOfBoundsException异常;

elementData(index);

由于ArrayList的底层是由数组实现的,所有获取元素直接调用数组的随机访问即可;

1public E get(int index) {
2    rangeCheck(index);
3
4    return elementData(index);
5}

5、Iterator

5.1 前言

Java开发初级工程师在使用for遍历集合的时候,由于使用不正确的API方法对集合进行remove()操作,经常会抛出java.util.ConcurrentModificationException,因此我们来从源码的层面仔细的探究一下为什么会产生ConcurrentModificationException以及如何解决这个异常!foreach循环又称增强for循环,是jdk1.5为了简化数组或者和容器遍历而产生,foreach循环的适用范围:对于任何实现了Iterable接口的容器都可以使用foreach循环。foreach语法的冒号后面可以有两种类型,一种是数组类型;一种是实现了Iterable接口的类,因此在使用foreach循环遍历ArrayList的时候,可以看作就是在使用List的迭代器进行遍历

如下代码,foreach遍历list集合时,调用list.remove(s)方法,控制台输出了预期ConcurrentModificationException异常

5.2 ArrayList中Iterator

本质上返回的是一个Itr实例化对象

1public Iterator<E> iterator() {
2    return new Itr();
3}

ArrayList定义了一个Itr内部类实现了Iterator接口,Itr内部有三个属性。

cursor:代表的是下一个访问的元素下标;

lastRet:代表的是上一个访问元素的下标,默认值为-1;

expectedModCount:代表的是对ArrayList修改的次数,初始值等于modCount

 1/**
 2 * An optimized version of AbstractList.Itr
 3*/
 4private class Itr implements Iterator<E> {
 5    int cursor;       // index of next element to return
 6    int lastRet = -1// index of last element returned; -1 if no such
 7    int expectedModCount = modCount;
 8
 9    Itr() {}
10}

hasNext();

判断是否存在下一个元素,返回值只要cursor下一个元素的下标不等于集合的元素个数size就返回true,其他情况返回false

1public boolean hasNext() {
2    return cursor != size;
3}

next() ;

获取集合中的下一个元素

checkForComodification();

判断modCount与expectedModCount是否相等,不相等抛出ConcurrentModificationException异常

i = cursor;i >= size;i >= elementData.length

判断cursor的大小,如果cursor的值大于集合中元素的个数,抛出NoSuchElementException异常;如果cursor大于了数组的长度抛出ConcurrentModificationException异常。

cursor = i + 1;lastRet = i

如果上述情况均满足,则正常获取下一个元素,cursor和lastRet都自增1

 1public E next() {
 2    checkForComodification();
 3    int i = cursor;
 4    if (i >= size)
 5        throw new NoSuchElementException();
 6    Object[] elementData = ArrayList.this.elementData;
 7    if (i >= elementData.length)
 8        throw new ConcurrentModificationException();
 9    cursor = i + 1;
10    return (E) elementData[lastRet = i];
11}
12
13final void checkForComodification() {
14    if (modCount != expectedModCount)
15        throw new ConcurrentModificationException();
16}

lastRet < 0;

如果lastRet 小于0,则抛出IllegalStateException异常,由此可以看出,调用remove()之前,必须先调用next()方法重置lastRet的值,否则在lastRet默认值为-1的情况下,对集合中的元素进行移除会直接抛出异常;

checkForComodification();

判断modCount与expectedModCount是否相等,不相等抛出ConcurrentModificationException异常

ArrayList.this.remove(lastRet);

调用ArrayList的remove方法,移除元素

cursor = lastRet;lastRet = -1;expectedModCount = modCount;

将lastRet的值赋值给cursor,相当于-1;将lastRet 重置为-1;将modCount赋值给expectedModCount

 1public void remove() {
 2    if (lastRet < 0)
 3        throw new IllegalStateException();
 4    checkForComodification();
 5
 6    try {
 7        ArrayList.this.remove(lastRet);
 8        cursor = lastRet;
 9        lastRet = -1;
10        // 注意这一步非常关键,重新赋值modCount给expectedModCount,是不会出现ConcurrentModificationException异常的关键
11        expectedModCount = modCount;
12    } catch (IndexOutOfBoundsException ex) {
13        throw new ConcurrentModificationException();
14    }
15}
16
17final void checkForComodification() {
18    if (modCount != expectedModCount)
19        throw new ConcurrentModificationException();
20}

5.3 图解

初始化时Itr中的cursor=0;lastRet=-1;modCount = 4;expectedModCount=4;注意modCount 的值是在调用ArrayList方法的add()方法时进行modCount++;而expectedModCount的值Itr初始化时,默认等于modCount。

当foreach循环到D元素的时候,cursor=2;lastRet=1;modCount = 4;expectedModCount=4;

当调用list.remove(s)移除了元素“D”之后,cursor=2;lastRet=1;modCount = 5;expectedModCount=4;注意此处调用的时ArrayList的remove()方法

由于ArrayList的foreach遍历实质上相当于ArrayList中的Itr迭代,所以在Itr内部类中的next()方法被调用时,checkForComodification()方法中比较modCount 和 expectedModCount的值,发现二者不相等,则抛出ConcurrentModificationException异常!

 1public E next() {
 2    // 检查modCount 与 expectedModCount的值是否相等
 3    checkForComodification();
 4    int i = cursor;
 5    if (i >= size)
 6        throw new NoSuchElementException();
 7    Object[] elementData = ArrayList.this.elementData;
 8    if (i >= elementData.length)
 9        throw new ConcurrentModificationException();
10    cursor = i + 1;
11    return (E) elementData[lastRet = i];
12}
13// 检查modCount 与 expectedModCount的值是否相等
14final void checkForComodification() {
15    // 不相等则抛出ConcurrentModificationException异常
16    if (modCount != expectedModCount)
17        throw new ConcurrentModificationException();
18}

5.4 异常解决

调用Iterator的remove()方法即可

 1public void remove() {
 2    // 需要注意的是lastRet<0会直接抛出异常,lastRet在初始化时和移除元素之后会被赋值为-1
 3    if (lastRet < 0)
 4        throw new IllegalStateException();
 5    checkForComodification();
 6
 7    try {
 8        ArrayList.this.remove(lastRet);
 9        cursor = lastRet;
10        // 将lastRet重置为-1
11        lastRet = -1;
12        // 此处会对expectedModCount重新赋值,因而不会出现expectedModCount!=modCount
13        expectedModCount = modCount;
14    } catch (IndexOutOfBoundsException ex) {
15        throw new ConcurrentModificationException();
16    }
17}