赞
踩
ArrayList
继承自 AbstractList
,AbstractList
实现了 List
接口的部分方法,为 List
的具体实现类提供了方便。AbstractList
是一个抽象类,它实现了 Collection
接口,也就间接实现了 Iterable
接口。List
接口继承自 Collection
接口,Collection
继承自 Iterable
接口。List
接口代表有序的队列RandomAccess
是一个标记接口,标识 ArrayList
支持快速随机访问,可以通过索引直接访问元素。Cloneable
和 Serializable
也是标记接口,前者表示可复制克隆,后者表示可序列化。Collection
接口,定义了集合的基本操作。List
、Set
、Queue
等子接口,分别定义了有序、无序、队列等特性。AbstractList
、AbstractSet
等抽象类为子接口提供部分方法实现。ArrayList
、LinkedList
、HashMap
等。ArrayList 底层是通过动态数组(可自动扩容)实现的,它的工作原理如下:
ArrayList 的源码注释如下:
/** * 默认初始容量为 10 */ private static final int DEFAULT_CAPACITY = 10; /** * 用于储存元素的数组缓冲区 */ transient Object[] elementData; /** * ArrayList 中元素的数量 */ private int size; // 其他方法...
可以看出,ArrayList 内部通过一个 Object 数组存储元素,size 变量记录当前元素数量,通过自动扩容机制来支持动态增长。这种基于动态数组的实现,决定了 ArrayList 查询快、增删慢的特点。
在 Java JDK 1.8 中,ArrayList 的默认初始容量 10 是在第一次通过无参构造函数创建 ArrayList 对象时初始化的,而不是在添加第一个元素时初始化。
ArrayList 有三种构造方式:
ArrayList()
/**
* 构造一个初始容量为10的空列表
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
使用无参构造函数时,底层会初始化一个初始容量为 10 的空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
。
ArrayList(int initialCapacity)
/**
* 构造一个具有指定初始容量的空列表
*/
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);
}
}
使用这个指定容量的构造函数,会根据指定的容量初始化底层数组大小。
ArrayList(Collection<? extends E> c)
/**
* 构造一个包含指定collection的元素的列表,按迭代器返回元素的顺序
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray可能(错误地)不同时使用Object[]...
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 使用空数组替换以允许缓冲区进行正常增长
this.elementData = EMPTY_ELEMENTDATA;
}
}
使用这个构造函数会根据提供的集合的大小创建底层数组。
使用无参构造函数 ArrayList()
时,底层数组的初始容量才会被初始化为 10。其他两种构造函数要么根据用户指定的容量初始化,要么根据提供的集合大小初始化。而添加元素时则不会再初始化容量,只是在容量不足时按照需求扩容。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal(size + 1)
: 这一行代码调用了ensureCapacityInternal
方法,它用于确保ArrayList
内部数组的容量足够来存放新增的元素。如果需要增加ArrayList
的容量,它会创建一个新的更大的数组,并将原来的元素复制到新数组中。
elementData[size++] = e;
: 这一行代码将新的元素e
添加到ArrayList
的内部数组elementData
中,并且将size
变量递增,表示列表中的元素数量增加了一个。这里使用size++
是因为要先将元素添加到size
所指示的位置,然后再递增size
,以便下次添加元素时能够添加到正确的位置。
return true;
: 最后,方法返回true
,表示添加操作成功。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
这段代码是ArrayList
中的ensureCapacityInternal(int minCapacity)
方法的部分源码
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
: 这一行代码用于检查elementData
是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,该常量表示一个空数组。如果是空数组,说明当前ArrayList
没有初始化容量,需要根据默认容量和需要添加的最小容量来确定实际容量。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
: 如果elementData
是空数组,则会根据默认容量DEFAULT_CAPACITY
和需要添加的最小容量来确定实际的最小容量。DEFAULT_CAPACITY
是ArrayList
默认的初始容量大小。
ensureExplicitCapacity(minCapacity);
: 接下来调用ensureExplicitCapacity(minCapacity)
方法来确保ArrayList
的内部数组容量能够容纳至少minCapacity
个元素。这个方法会比较当前数组的容量和需要的最小容量,如果当前容量不足,则会扩大内部数组的容量以满足需求。
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
-这段代码是ArrayList
中的ensureExplicitCapacity(int minCapacity)
方法的部分源码
modCount++;
: 这一行代码递增了modCount
变量的值。modCount
用于记录结构修改次数,主要用于在迭代过程中检测并发修改。每当进行结构性修改时(如添加或移除元素),modCount
会递增,表示ArrayList
的结构发生了变化。
if (minCapacity - elementData.length > 0) grow(minCapacity);
: 这里使用了溢出安全的计算方式来判断是否需要扩容内部数组。如果当前容量不足以容纳最小容量minCapacity
个元素,则需要调用grow(minCapacity)
方法来扩大内部数组的容量。
grow(int minCapacity)
: 当需要扩容时,会调用grow(int minCapacity)
方法来实现容量的扩充。grow()
方法会根据需要的最小容量来确定新的容量大小,并将原来的元素复制到新的更大的数组中。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
这段代码是ArrayList
中的grow(int minCapacity)
方法的部分源码
int oldCapacity = elementData.length;
: 获取当前内部数组的容量,即原来的容量大小。
int newCapacity = oldCapacity + (oldCapacity >> 1);
: 原来的容量大小计算出新的容量大小。新的容量大小会是原来容量的1.5倍,通过右移一位实现了除以2的操作。原来容量大小 + 容量大小的一半
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
: 如果新的容量仍然小于需要的最小容量minCapacity
,则将新的容量调整为minCapacity
,确保容量能够满足需求。
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity);
: 检查新的容量是否超出了最大数组大小MAX_ARRAY_SIZE
,如果超出则调用hugeCapacity()
方法来确定合适的容量大小,这是为了避免数组大小超出Java虚拟机的限制。
elementData = Arrays.copyOf(elementData, newCapacity);
: 最后,使用Arrays.copyOf()
方法将原来的元素复制到新的更大的数组中,完成了内部数组的扩容过程。
grow()
方法主要负责处理ArrayList
内部数组的扩容问题。它会根据旧的容量大小计算出新的容量大小,并根据需要调整为满足最小容量要求的合适大小。然后将原来的元素复制到新的更大的数组中,完成数组的扩容操作。这样可以确保在需要添加大量元素时,ArrayList
内部数组能够容纳足够多的元素。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
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
return oldValue;
}
这段代码是ArrayList
中的remove(int index)
方法的部分源码
rangeCheck(index)
: 这一行代码调用了rangeCheck
方法来检查索引是否越界,确保要移除的元素在有效范围内。如果索引不在有效范围内,会抛出IndexOutOfBoundsException
。
modCount++
: 这里递增了modCount
变量的值。modCount
用于记录结构修改次数,主要用于在迭代过程中检测并发修改。每当进行结构性修改时(如添加或移除元素),modCount
会递增,表示ArrayList
的结构发生了变化。
E oldValue = elementData(index)
: 这一行代码获取要移除的元素,并将其保存为oldValue
。
int numMoved = size - index - 1;
: 计算需要向前移动的元素个数,即原始数组中位于被删除元素后面的元素个数。
System.arraycopy(elementData, index+1, elementData, index, numMoved)
: 如果存在需要向前移动的元素,则使用System.arraycopy
方法将这些元素向前移动,以填补被删除元素的位置。
elementData[--size] = null
: 将列表中最后一个元素置空,以便让GC进行清理工作。
返回oldValue
:返回被删除的元素的值。
通过以上步骤,remove()
方法实现了在指定位置上删除元素的功能。它首先进行了参数的合法性检查,然后递增了modCount
,接着移动需要向前移动的元素,将列表中最后一个元素置空,并返回被删除的元素的值。
public boolean remove(Object o) { 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; }
这段代码是ArrayList
中的remove(Object o)
方法的部分源码
如果传入的对象 o
为空(null):
elementData
数组,寻找为 null 的元素。fastRemove(index)
方法进行删除,并返回 true。如果传入的对象 o
不为空(non-null):
elementData
数组,寻找与传入对象相等的元素。fastRemove(index)
方法进行删除,并返回 true。如果未找到符合条件的元素,则直接返回 false。
fastRemove(index)
方法用于快速删除指定索引位置的元素。通过这种方式,remove(Object o)
方法能够在列表中移除满足特定条件的第一个元素,并返回是否成功移除的布尔值。
ArrayList存在线程安全问题的本质在于其内部的elementData、size和modCount等变量,在进行各种操作时没有加锁,并且这些变量的类型也不是volatile的。因此,如果多个线程对这些变量进行操作,则可能出现值被覆盖的情况。需要强调的是,只有当ArrayList作为共享变量时,才会出现线程安全问题;当ArrayList是方法内的局部变量时,则不存在线程安全问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。