JDK1.8源码-05-java.util.ArrayList

JDK1.8源码-05-java.util.ArrayList

本篇重点是介绍ArrayList 类是如何实现的。

1. 定义

ArrayList是一个用数组实现的集合类,支持随机访问,元素有序且可以重复

1
2
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

关系图所下图所示:(IDEA中快捷键 Ctrl + shift + alt + u)mark

  1. 实现了RandomAccess接口

这是一个标记接口,一般此标记接口用于List实现,以表明它们支持快速(通常是恒定时间)的随机访问。

该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表的时候提供更好的性能。

比如在工具类Collections(这里工具类后面会详细说明),应用二分查找法时判断是否实现了RandomAccess 接口:

1
2
3
4
5
6
7
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
  1. 实现了Cloneable接口

这个类是java.lang.Cloneable,前面我们讲解深拷贝和浅拷贝原理的时候,我们介绍了浅拷贝可以通过调用 Object.clone() 方法来实现,但是调用该方法的对象必须要实现Cloneable接口,否则会抛出 CloneNoSupportException异常。

  1. 实现 Serializable 接口

这个也是标记接口,表示能被序列化。

  1. 实现List接口

这个接口是List类集合的上层接口,定义了实现该接口的类都必须要实现的一组方法(ArrayList也不例外)。如下图所示:

mark

下面我们会对这一系列方法的实现做详细介绍。

2. 字段属性

1
2
3
4
5
6
7
8
9
10
11
12
// 集合的默认初始大小
private static final int DEFAULT_CAPACITY = 10;
// 空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储 ArrayList集合的元素,集合的长度即这个数组的长度
// 1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
// 2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
transient Object[] elementData;
// 表示集合的长度
private int size;

3. 构造方法

3.1 无参构造

此无参构造函数将创建一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA声明的数组,注意这时候的初始容量是0,而不是大家以为的10.

注意:根据默认构造函数创建的集合,ArrayList list = new ArrayList();此时集合长度是0.

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

3.2 有参构造

初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于0时,抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
// 当大于0时,给定多少那就创建多大的数组;
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
// 当等于0时,创建一个空数组;
this.elementData = EMPTY_ELEMENTDATA;
} else {
// 当小于0时,抛出异常。
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

这是将已有的集合复制到ArrayList集合中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
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;
}
}

4. 添加元素

通过前面的字段属性和构造函数,我们知道ArrayList 集合是有数组构成的,那么向ArrayList 中添加元素,也就是向数组赋值。

我们知道一个数组的声明是能确定大小的,而使用ArrayList 时,好像能添加任意多个元素,这就涉及到了数组的动态扩容。

扩容的核心方法就是调用我们前面讲过的Arrays.copyOf方法,创建一个更大的数组,然后将原数组元素拷贝过去即可。

下面我们来看看具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
// 添加元素之前,首先确定集合的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

如上所示,在通过调用add方法添加元素之前,首先需要调用ensureCapacityInternal 方法来确定集合的大小,如果集合满了,就要进行扩容的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;

if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}

private void ensureCapacityInternal(int minCapacity) {
// 这里的minCapacity是集合当前的大小 + 1 = size + 1
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// elementData是用来实际存储元素的数组,注意数组的大小和集合的大小是不相等的。(前面的size是集合的大小)
// 如果数组是空的话,则从size + 1和 默认的10中取最大的。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
// 首先对修改次数modCount+1
// 这里的modCount是给ArrayList的迭代器使用的,在并发操作被修改的时候,提供快速失败的行为(保证modCount在迭代期间不变,否则跑出ConcurrentModificationException异常)
modCount++;

// overflow-conscious code
// 接着判断minCapacity是否大于当前ArrayList内部数组长度,大于的话调用grow方法对数组elementData扩容,grow方法代码如下所示
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

并发异常源码在ArrayList第865行:

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

grow方法代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
// overflow-conscious code
// 拿到数组的原始长度
int oldCapacity = elementData.length;
// 新的数组长度等于原来数组长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 当新数组长度仍然比minCapacity小,则为保证最小长度,新数组等于minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 当新得到的数组长度比MAX_ARRAY_SIZE大的时候,调用hugeCapacity来处理大数组
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 调用Arrays.copyOf将原数组拷贝到一个大小为newCapacity大小的新数组中(注意是拷贝引用)
elementData = Arrays.copyOf(elementData, newCapacity);
}


private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// minCapacity > MAX_ARRAY_SIZE,则新数组大小为Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

总结一下ArrayList集合添加元素:

  • 当通过 ArrayList() 构造一个空集合的时候,初始长度是0,第1次添加元素的时候,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。

  • 第2次添加元素的时候,集合不为空,而且由于集合的长度size+1是小于数组长度10的,所以直接添加元素到数组的第二个位置,不用扩容。

  • 第11次添加元素的时候,此时size + 1 = 11,而数组长度是10,这时候创建一个长度是10 + 10 *0.5 = 15的数组(扩容1.5倍),然后将原数组元素的引用拷贝到新数组。并将第11次添加的元素赋值到新数组下标为10的位置。

  • 第 Integer.MAX_VALUE - 8 = 2147483639,然后2147483639 / 1.5 = 1431655759(这个数是要进行扩容) 次添加元素, 为了防止溢出,此时会直接创建一个1431655759 + 1大小的数组,这样一直,每次添加一个元素,都只扩大一个范围。

  • 第 Integer.MAX_VALUE - 7 次添加元素的时候,创建一个大小为 Integer.MAX_VALUE 的数组,再进行元素添加。

  • 第 Integer.MAX_VALUE + 1 次添加元素的时候,抛出OutOfMemoryError 异常。

  • 注意:能向集合元素添加null的,因为数组中可以有null值的存在。

1
2
3
4
ArrayList list = new ArrayList();
list.add(null);
list.add(1);
System.out.println(list.size());//2

5. 删除元素

  1. 根据索引删除元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/

public E remove(int index) {
// 判断给定索引的范围,超过集合大小则抛出异常
rangeCheck(index);

modCount++;
// 得到索引处要删除的元素
E oldValue = elementData(index);

int numMoved = size - index - 1;
if (numMoved > 0)
// size - index -1 > 0 表示 0 <= index < (size - 1),即索引不是最后一个元素。
// 通过System.arraycopy() 把数组elementData的下标index + 1之后长度为numMoved的元素拷贝到从index开始的位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 如果是最后一个元素,将数组最后一个元素置为 null,便于垃圾回收
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

remove(int index)方法表示删除索引Index处的元素

  • 首先通过rangeCheck(index)判断给定索引的范围是否符合要求。

  • 接着通过System.arraycopy 方法对数组进行自身的拷贝。(这个方法可以查看上一篇Arrays的博客。

  1. 直接删除指定元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public boolean remove(Object o) {
if (o == null) {
// 如果要删除的元素是null的话
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
// 通过fastRemove删除元素
fastRemove(index);
return true;
}
} else {
// 不为空的情况下,通过equals方法判断对象是否相等
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
// 通过fastRemove删除元素
fastRemove(index);
return true;
}
}
return false;
}

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
}

remove(Object o) 方法是删除第一次出现的该元素,然后通过System.arraycopy进行数组的自身拷贝。

6. 修改元素

通过调用set(int index, E element) 方法在指定索引index处的元素替换为element。并且返回原数组的元素。

1
2
3
4
5
6
7
8
9
10
  public E set(int index, E element) {
// 判断索引合法性
rangeCheck(index);
// 获得原数组指定索引的元素
E oldValue = elementData(index);
//将指定所引处的元素替换为 element
elementData[index] = element;
//返回原数组索引元素
return oldValue;
}

通过rangeCheck(index)来检查索引的范围是否越界:

1
2
3
4
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • 索引是负数的时候,会抛出java.lang.ArrayIndexOutOfBoundsException异常。
  • 索引是大于集合长度的时候,会抛出IndexOutOfBoundsException 异常。

7. 查找元素

  1. 根据索引查找元素

get(int index)

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

return elementData(index);
}

同理,首先还是判断给定索引的合理性,然后直接返回处于该下标位置的数组元素。

  1. 根据元素来查找索引

注意:indexOf(Object o) 方法是返回第一次出现该元素的下标,如果没有则返回 -1。

​ lastIndexOf(Object o) 方法是返回最后一次出现该元素的下标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int indexOf(Object o) {
if (o == null) {
// 如果要找null,一个一个找,找到第一个就返回索引
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
// 不是null的话,一个一个找,找到第一个就返回索引
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
// 找不到返回-1
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
// 找不到返回-1
return -1;
}

8. 遍历集合

  1. 普通for循环遍历

前面我们介绍查找元素的时候,知道可以通过get(int index)方法,根据索引查找元素,那么遍历同理。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
  1. 迭代器iterator

先看看具体怎么用:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

Iterator<String> it = list.iterator();
while (it.hasNext()){
String str = it.next();
System.out.println(str);
}
}

在介绍ArrayList的时候,我们知道该类实现了List接口,而List接口又继承了Collection接口,Collection接口又继承了Iterable接口,该接口中有个Iterator<T> iterator()

mark

它能获取Iterator对象,能用该对象进行集合的遍历,那么为什么能用该对象进行集合遍历?我们再看看ArrayList中该方法的实现。

1
2
3
4
5
6
7
8
9
10
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}

该方法返回的是一个Itr对象,这个类是ArrayList的内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
// 游标: 下一个要返回元素的索引
int cursor; // index of next element to return
// 返回最后一个元素的索引,如果没有这样的话返回-1
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;

// 通过游标判断是否还有下一个元素。
public boolean hasNext() {
return cursor != size;
}

@SuppressWarnings("unchecked")
public E next() {
// 这里多线程操作,如果迭代器进行元素迭代的同时进行增加和删除操作,会抛出异常
checkForComodification();
int i = cursor;
// 当前游标超过集合size
if (i >= size)
throw new NoSuchElementException();
// 拿到当前的元素
Object[] elementData = ArrayList.this.elementData;
// 当前游标超过数组长度,抛出多线程异常(保证线程操作下游标只能移动一位)
if (i >= elementData.length)
throw new ConcurrentModificationException();
// 游标向后移动一位
cursor = i + 1;
// 返回索引为i处的元素,并将lastRet赋值为i
return (E) elementData[lastRet = i];
}

public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
// 调用ArrayList的remove方法删除元素
ArrayList.this.remove(lastRet);
// 游标指向删除元素的位置,本来是lastRet + 1的,这里删除一个元素,然后游标就不变了
cursor = lastRet;
// lastRet恢复默认值-1
lastRet = -1;
// expectedModCount和modCount同步,因为进行了add和remove操作,modCount会加1
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
// 便于进行forEach遍历操作
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}

final void checkForComodification() {
//前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的modCount++.
// 这也就是说不能在迭代器进行元素迭代的时候进行增加或者删除操作,否则会抛出异常。
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

注意在进行next()方法调用的时候,会进行 checkForComodification() 调用,该方法表示迭代器进行元素迭代的时候,如果同时进行增加或者删除的操作,会抛出 ConcurrentModificationException 异常。

比如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

Iterator<String> it = list.iterator();
while (it.hasNext()){
String str = it.next();
System.out.println(str);

// 集合遍历时进行删除或者新增操作,
// 都会抛出 ConcurrentModificationException 异常
// list.remove(str);
// list.add(str);

// 修改操作不会造成异常
list.set(0,str);
System.out.println(list);
}
}

解决上述的方法是不调用ArrayList.remove() 方法,转而使用迭代器的remove()方法:

1
2
3
4
5
6
7
Iterator<String> it = list.iterator();
while(it.hasNext()){
String str = it.next();
System.out.print(str+" ");
//list.remove(str);//集合遍历时进行删除或者新增操作,都会抛出 ConcurrentModificationException 异常
it.remove();
}

注意:

  • 迭代器只能向后遍历,不能向前遍历,

  • 迭代器能够删除元素,但是不能新增元素。

  1. 迭代器的变种forEach
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

for (String str : list) {
System.out.println(str);
}
}

这种语法可以看成是 JDK 的一种语法糖,通过反编译class文件,我们可以看到生成的java文件,其具体实现还是通过调用iterator迭代器进行遍历的。如下:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

String str;
for (Iterator iterator1 = list.iterator(); iterator1.hasNext(); System.out.print((new StringBuilder(String.valueOf(str))).append(" ").toString()))
str = (String)iterator1.next(); // a b c
}
  1. 迭代器ListIterator

先看看具体的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");


ListIterator<String> listIt = list.listIterator();

// 向后遍历
while (listIt.hasNext()){
System.out.println(listIt.next()); // a b c
}

// 向前遍历
while (listIt.hasPrevious()){
System.out.println(listIt.previous()); // c b a
}
}

还能一边遍历,一边进行新增或者删除操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
ListIterator<String> listIt = list.listIterator();

//向后遍历
while(listIt.hasNext()){
System.out.print(listIt.next()+" ");//a b c
listIt.add("1");//在每一个元素后面增加一个元素 "1"
}

//向后前遍历,此时由于上面进行了向后遍历,游标已经指向了最后一个元素,所以此处向前遍历能有值
while(listIt.hasPrevious()){
System.out.print(listIt.previous()+" ");//1 c 1 b 1 a
}
}

 也就是说相比于 Iterator 迭代器,这里的 ListIterator 多出了能向前迭代,以及能够新增元素

下面我们看看具体的源码实现:

1
2
3
// 对于  Iterator 迭代器,我们查看 JDK 源码,发现还有 ListIterator 接口继承了 Iterator:

public interface ListIterator<E> extends Iterator<E>

mark

同时可以看到的是在ArrayList类中,有如下方法可以获得ListIterator 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
   /**
* An optimized version of AbstractList.ListItr
*/

// 注意这里的ListItr继承了另一个内部类Itr
private class ListItr extends Itr implements ListIterator<E> {
// 构造函数 - 》 进行游标的初始化
ListItr(int index) {
super();
cursor = index;
}

// 判断是否有上一个元素
public boolean hasPrevious() {
return cursor != 0;
}

// 返回下一个元素的索引
public int nextIndex() {
return cursor;
}

// 返回上一个元素的索引
public int previousIndex() {
return cursor - 1;
}

@SuppressWarnings("unchecked")
// 获取当前索引的上一个元素
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i; //游标指向上一个元素
return (E) elementData[lastRet = i]; //返回上一个元素的值
}

// 修改元素
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
// 调用ArrayList的set方法将元素e赋值给lastRet索引的位置
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

// 相比于迭代器Iterator ,这里多了一个新增操作
public void add(E e) {
checkForComodification();

try {
int i = cursor;
// 调用ArrayList中add方法将e元素赋值给i索引的位置
ArrayList.this.add(i, e);
// 游标移动到下一个位置
cursor = i + 1;
lastRet = -1;
// 同步修改次数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}

9. SubList

在ArrayList中有这样一个方法:

1
2
3
4
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}

作用:返回从fromIndex(包括)开始的下标,到toIndex(不包括)结束的下标之间的元素视图。 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();

list.add("a");
list.add("b");
list.add("c");

List<String> subList = list.subList(0, 1);
for (String str : subList){
System.out.println(str); // a
}
}

这里出现了SubList类,这也是ArrayList中的一个内部类。

注意:返回的是原集合的视图,也就是说,如果对subList出来的集合进行修改或者新增操作的话,那么原始集合也会发生同样的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();

list.add("a");
list.add("b");
list.add("c");

List<String> subList = list.subList(0, 1);
for (String str : subList){
System.out.println(str);
}

// 注意:返回的是原集合的视图,
// 也就是说 如果对 subList 出来的集合进行修改或新增操作,那么原始集合也会发生同样的操作。
subList.add("d");
System.out.println(subList.size()); // 2
System.out.println(list.size()); // 4 , 原始集合的长度也增加了
}

在源码中可以清晰得到原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
// 调用subList的内部类
return new SubList(this, 0, fromIndex, toIndex);
}


private class SubList extends AbstractList<E> implements RandomAccess {
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
// 原来的集合传过来的引用,sublist直接就使用原来的引用,所以修改的是原来的元素
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
}

那如果想要独立出来一个集合,解决办法如下:

1
List<String> subList = new ArrayList<>(list.subList(0, 1));
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();

list.add("a");
list.add("b");
list.add("c");

// 单独新创建一个集合
List<String> subList1 = new ArrayList<>(list.subList(0, 1));
subList1.add("d");
System.out.println(subList1.size()); // 2
System.out.println(list.size()); // 3 , 原始集合的长度没有增加
}

10. size

1
2
3
4
5
6
7
8
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return size;
}

注意:返回的是集合长度,而不是数组长度,这里的size是在源码中定义的全局变量。

11. isEmpty()

1
2
3
4
5
6
7
8
/**
* Returns <tt>true</tt> if this list contains no elements.
*
* @return <tt>true</tt> if this list contains no elements
*/
public boolean isEmpty() {
return size == 0;
}

集合为空 : 返回true

集合不为空: 返回false

12. trimToSize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
// 空的话就清0
? EMPTY_ELEMENTDATA
// 否则返回size长度大小的集合
: Arrays.copyOf(elementData, size);
}
}

该方法的作用是回收多余的内存。

也就是说一旦我们确定集合不再加添加多余的元素之后,调用trimToSize()方法会将实现集合的数组大小刚好调整为集合元素的大小。

需要注意的是:如果调用该方法的话,需要确定在不会添加元素之后再使用。(该方法会话时间来复制数组中的元素。)

参考文档:

https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信