前言
从这一章开始,将会进入JAVA集合部分的分析,包含了大部分集合大家族,如常用的ArrayList、LinkedList、HashSet、HashMap、TreeSet、TreeMap等等,还有它们的迭代器,如Iterator、listIterator、Collections工具类,然后可能还有对不常用的集合做简要分析,集合之间对比分析优点、缺点。这样,才能够让我们对Java的集合部分有了全面的了解,在编码过程中能更好的知道用什么集合。
ArrayList 结构图
ArrayList很常用,并且它的代码并不多。按照归类主要分为图中的几个部分。
(1)属性
// ArrayList的默认容量
private static final int DEFAULT_CAPACITY = 10;
// ArrayList 内部维持的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// ArrayList内部维持的默认空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList维持的可变数组;ArrayList的核心属性。
transient Object[] elementData;
// 元素在容器中的个数,与elementData.length有区别。
private int size;
// 容器被修改的次数
protected transient int modCount = 0;
// ArrayList内部维持的最大数组长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
(2)构造器
// 无参构造
ArrayList()
// 指定容量构造
ArrayList(int initialCapacity)
// 将指定集合构造成ArrayList
ArrayList(Collection<? extends E> c)
(3)扩容机制
ensureCapacity(int minCapacity)
ensureCapacityInternal(int minCapacity)
ensureExplicitCapacity(int minCapacity)
grow(int minCapacity)
hugeCapacity(int minCapacity)
(4)常用API
a. 增
clone()
add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
b. 删
clear()
trimToSize()
remove(int index)
remove(Object o) fastRemove(int index)
removeRange(int fromIndex, int toIndex)
removeAll(Collection<?> c)
retainAll(Collection<?> c)
batchRemove(Collection<?> c, boolean complement)
c. 改
set(int index, E element)
d. 查
size()
isEmpty()
get(int index)
indexOf(Object o)
lastIndexOf(Object o)
e . 判断
contains(Object o)
f. 转换
toArray()
toArray(T[] a)
(5)内部类
迭代器: Itr
ListItr
集合: SubList
源码分析
ArrayList 是编码过程中经常使用的一个集合。它的底层维护了一个数组 elementData。
所有的操作都是基于这个数组来操作的。与StringBuffer 或 StringBuilder 有点类似。底层元素是一样,其内部也有扩容机制,以及各种各样的API,不同的是ArrayList的包容性更强,可以装载任何相同的一组对象。下面重点分析它的扩容机制 和 常用的API 和迭代器,以及为什么有扩容机制,怎么实现的,迭代器是怎样工作的,有哪些功能。
ArrayList最初被定义为一种容器,因此它有很多对外的API,如上述所分类的【增】、【删】、【查】、【改】、【判断】、【装换】。下面先从分析API开始,逐渐引出扩容机制。
(1)增 与 扩容机制
clone()
克隆方法是调用的父类的本地方法,实现的浅拷贝,关于浅拷贝和深拷贝,后面专门做一篇文章进行研究。这里不再赘述。下面贴出源码:
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*翻译:返回这个实例的浅拷贝。元素本身内容不会被拷贝
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone(); // 调用了父类的本地方法实现克隆
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
add(E e)、add(int index, E element)
这两个方法都是增加,其不同点
add(E e)方法指添加一个元素,添加位置是末尾。
add(int index, E element)方法添加一个元素,添加位置是列表中下标为index的地方。
这两个方法都会涉及到改变ArrayList内部的数据结构,使其增加一个长度,下面进入源码分析:
add(E e)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! //确保足够的数组长度
elementData[size++] = e; // 将元素添加到最后一个位置
return true;
}
add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index); //下标检查是否越界
ensureCapacityInternal(size + 1); // Increments modCount!! // 确保足够的数组长度
System.arraycopy(elementData, index, elementData, index + 1, // 从指定位置添加元素,其后依次后移。
size - index);
elementData[index] = element;
size++;
}
上面都用到了扩容方法ensureCapacityInternal(size + 1),我们下面开始分析一下ArrayList的扩容机制。
扩容机制
扩容机制的诞生始于元素的添加,也就是对应的add方法,只有当容量长度不够时,才会涉及到扩容。其他功能【改】、【删】、【查】、【判断】、【转换】等等都不会涉及到扩容机制。
对外API手动扩容: ensureCapacity(int minCapacity)
自动扩容:ensureCapacityInternal(int minCapacity)
扩容判断: ensureExplicitCapacity(int minCapacity)
扩容核心: grow(int minCapacity) //小中容量
扩容核心: hugeCapacity(int minCapacity) //大容量
扩容有四个方法,最后两个为扩容核心实现方法。从外到内,分析一下:
ensureCapacity(int minCapacity)
这个方法提供给调用者,可手动扩容。
public void ensureCapacity(int minCapacity) {
// 计算最小扩展长度,如果是用无参构造函数,当前数组为默认空数组,那么扩展长度为10。
// 如果是有参构造或者其他,则扩展长度为0.
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);
}
}
实例演示
通过反射来查看其内部私有属性的值。
ArrayList list = new ArrayList();
Class c = list.getClass();
ArrayList list2 = (ArrayList) c.newInstance();
Field f = c.getDeclaredField("elementData");
f.setAccessible(true);
list2.add(1);
list2.ensureCapacity(100); // 手动调用扩容
Object[] element = (Object[]) f.get(list2);
System.out.println(list2.size());
System.out.println(element.length);
结果:1
100
ensureCapacityInternal(int minCapacity)
这个方法是ArrayList内部调用,相当于自动扩容,当其他方法需要扩容时,会调用该方法。扩容规则在其源码中。
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 如果为无参构造的默认空数组对象时进行扩容。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 最小扩容容量为10.
}
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity(int minCapacity)
上面的方法是计算容器所需的最小容量,这个条件是判断是否触发扩容。
// 关于modCount,ArrayList开头有一句话:
A structural modification is
* any operation that adds or deletes one or more elements, or explicitly
* resizes the backing array; merely setting the value of an element is not
* a structural modification.
* 翻译: 结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 这个变量计算当前ArrayList容器结构上被修改的次数,为什么放在扩容之前呢?
// 假如没有执行扩容,modCount也会+1,也就说明即时容器长度不变,
// 但是容器内的元素长度被改变了,如【增】【删】,modCount就会加1
// overflow-conscious code
if (minCapacity - elementData.length > 0) // 触发条件:所需最小容量 > 当前容量
grow(minCapacity);
}
grow(int minCapacity)
扩容核心实现方法:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容倍数为原容量的1.5倍
if (newCapacity - minCapacity < 0) // 如果扩容1.5倍后还是不够当前所需最小容量
newCapacity = minCapacity; // 新容量就扩容为当前所需最小容量
/** 从上面可以看出,并不是每次扩容都为原来的1.5倍。 **/
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 如果所需容量非常大,超过了 2^31-8,就重新计算扩容容量
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
hugeCapacity(int minCapacity)
大容量扩容计算:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? // 只有两种结果:2^31 ,和 2 ^ 31 - 8
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
回到【增】功能上来,主要有四个API;
add(E e)
add(int index, E element)
addAll(Collection<? extends E> c)
addAll(int index, Collection<? extends E> c)
前面两个属于单个增加,后面两个API属于多个增加。因此对于容量的增加需求是全方面的,从0到无穷大都有可能。
弄清楚了扩容机制,容器增加元素的相关方法也很简单了。上面介绍了add()方法。下面再来看下addAll()方法;
addAll(Collection<? extends E> c)
这个方法是将集合C中的所有元素,追加到当前容器的中,增加起始位置是末尾。
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
addAll(int index, Collection<? extends E> c)
这个方法作用一致,是将集合C中的所有元素,追加到当前容器的中,增加起始位置是指定的index下标开始。
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
(2)删 与 修剪
从add 方法和 扩容机制可以知道,在向容器中添加元素的时候,当容量不足时,可以使用扩容方法增加容量。那么与此相反的操作,当在删减元素时,当容量太大时,ArrayList 也提供了修剪容量的方法。因为如果容量远远大于容器内的元素数量,占有太多的内存空间,将会造成空间浪费。
下面看一下与增元素和扩容机制 相反的 删减元素 与 修剪容量。
trimToSize() // 修剪容量至当前元素所占长度
clear() // 清空所有元素
remove(int index)
remove(Object o) fastRemove(int index)
removeRange(int fromIndex, int toIndex)
removeAll(Collection<?> c)
retainAll(Collection<?> c)
batchRemove(Collection<?> c, boolean complement)
trimToSize()
public void trimToSize() {
modCount++; // 容量长度的修改属于结构修改,modCount会增加
if (size < elementData.length) { // 如果小于才进行修剪容量,修剪为当前元素占有长度
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
clear()
public void clear() {
modCount++; // 元素的删减属于结构修改
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; // 将容器中的所有变量与堆中对象解绑,对象将被交给GC处理
size = 0;
}
remove(int index)
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; // 返回被移除的元素
}
remove(Object o)
public boolean remove(Object o) {
// 因为本容器可以存放null对象,非空对象的判断一般是用equals方法
// 而对于null对象,它不能用equals方法,肯定是要单独判断的。
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;
}
removeAll(Collection<?> c) 、retainAll(Collection<?> c)和 batchRemove(Collection<?> c, boolean complement)
// removeAll(Collection<?> c)方法中是调用的batchRemove(Collection<?> c, boolean complement)
// 因此下面详细分析batchRemove(Collection<?> c, boolean complement);
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
retainAll(Collection<?> c)
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
batchRemove(Collection<?> c, boolean complement)
它有两个参数,第一个是集合接口,也就是可以传入List、Set、Vector、Queue等等
第二个参数指 是否包含。
两个参数合起来,就是指保留集合C中的所有元素还是集合C中的移除所有元素,
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r]; // 元素长度不变,将所需的元素,从第一个位置开始装填。
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
如果c.contains()抛出异常
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
// 装填完成后,由于装填数量 <= 容器元素初始数量,所以还要删除多余的尾部部分。
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified; // 如果元素移动后还是原来集合的样子,返回false
}
(3)改
set()
元素的修改不会影响到结构改变,因此这个方法不会使modCount增加。
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue; // 返回被换掉的元素
}
(4)查
ArrayList对于查找功能,提供了对长度、元素、下标三个维度的查找API,其源码思路和String,比较简单,这里不再赘述。
size()
isEmpty()
get(int index)
indexOf(Object o)
lastIndexOf(Object o)
(5)判断
contains(Object o)
其内部调用的indexOf方法,比较简单。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
(6)转换
toArray()
toArray(T[] a) // 这个方法暂时没看懂
toArray()
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
(7)迭代器
下面重点分析一下内部类,包含三个内部类,其中两个迭代器和一个ArrayList的子集合类subList。
迭代器: Itr
ListItr
集合: SubList
在ArrayList 集合里面有两个迭代器,和一个类似子集合功能的subList的一个类。
其中Itr 迭代器是实现了的Iterator,具有向后判断和移动的功能,而ListItr是Itr的子类,实现了ListIterator,除了在父亲能力的基础上,还增加了向前判断和移动的功能。
具体如下:
Itr
hasNext()
next()
remove()
ListItr
hasNext()
next()
remove()
hasPrevious()
previous()
set()
add()
对比上述两个迭代器可以看到,ListItr在父类的基础上增加了更多的功能,以满足对ArrayList 的各种操作。
上述两个迭代器中,都维持了下面三个变量
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
expectedModCount = modCount; 意思就是当调用者需要创建一个该集合迭代器的时候,其迭代器的已经被改过的次数将会交给expectedModCount,迭代器接过了对该集合的操作权力,并且只允许自己对集合修改,不允许外部对迭代器修改(包括集合自己)。
程序员:“我有一个集合,现在将它交给你”
迭代器:“放心吧,交给我负责”
所以,当一个集合的操作被交给了集合自己的迭代器。那么集合所有的改变结构的操作都将不能使用了,涉及结构修改的对象操作比如:
add() addAll() remove()等等,这些操作将会引起 modCount 的值改变。但是这个值得操作权力已经交给了迭代器。
所以当在使用迭代器遍历的过程中,有外部功能在对集合进行操作时,将会抛出ConcurrentModificationException异常,意思就是“当前并行操作异常“。所以迭代器遍历时,不允许外部操作,遍历过程中,每一个更改结构的操作,它都将根据modCount数值来判断,如果发现跟期望值不一样,就将会是快速失败的。
(8)subList 类
如果迭代器的出现是为了满足集合的重复的遍历需求,可以很方便的对集合进行操作。那么SubList 类的出现就是弥补集合的范围上的不足。
就像前面分析String 类源码一样,String 内部是数组,拥有各种各样花样繁多的操作,其中包括对子串的操作。如果当一个数组足够长,但是需要的数据范围并不必去遍历整个数组时,不论是String 还是 ArrayList 都需要提供一个可以限制范围查询或者遍历的操作。
String 类提供了 subString,同样的 subList 就是弥补 ArrayList 局部操作的一个内部类。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
这个是arrayList 集合的一个api,它将会返回一个该集合的子集合。参数int fromIndex, int toIndex 代表了从哪里截取到哪里。
这个subList 同样的和arrayList 一样继承了 AbstractList,也实现了几乎和ArrayList 一样的功能,包括迭代器。
部分源码
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
...
// subList的迭代器
public Iterator<E> iterator() {
return listIterator();
}
在subList 中各项功能,均是在子集合上的相应操作。它可以减少遍历的范围,提高效率。
总结
以上就是arrayList 的源码分析,有不对的地方请多多指教。上述的某些理解是个人的理解,只做学习参考使用。arrayList 是比较常见的集合之一,集合的大家族中有很多的成员。目前计划大家族的介绍做一篇分析,常用的单独做分析,不常用的归类做分析。