ArrayList的特性
<1>. 底层基于数组实现
<2>. 容器内元素有序可重复,可放null
<3>. 线程不安全
大概这几点,后续想到再追加。
如何认识ArrayList
本文章将自己按照源码一步一步实现自定义的ArrayList,并加以相应的中文描述和见解。
按照源码创建自定义的ArrayList
DIY ArrayList“全”代码
题目的“全”是代表目前写到的所有代码。
重复一次,代码是按Java源码写的(除FIXME标签的代码)。解析这个相当于解析源码,一步一步解析更容易理清源码,不被其它代码打乱。
代码有两个警告:什么 is not used这种,不必理会。
下面的代码目前只是做了一个简单的ArrayList容器,只有两个功能一个是往容器加元素,另一个是从容器获取元素。
FIXME标签是用来输出信息的,方便我们了解程序运行过程。
package spring.boot.study.javasourcecode;
import java.util.Arrays;
public class ArrayList_DIY<E> {
transient Object[] elementData;
transient int modCount = 0;
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private int size;
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 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_DIY(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);
}
}
public ArrayList_DIY() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
public void rangeCheck(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
@SuppressWarnings("unchecked")
private E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public int size() {
return size;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) {
// FIXME
if (ArrayListTest.isTrace()) {
System.out.println("需要进行扩容操作!");
}
grow(minCapacity);
}
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// FIXME
if (ArrayListTest.isTrace()) {
System.out.println("旧容量=" + oldCapacity);
System.out.println("新容量=" + newCapacity);
}
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
}
DIY ArrayList源码解析
public class ArrayList_DIY<E> {
类名是ArrayList_DIY,类名后的表示泛型类,在实例化对象时需要指定泛型类型,比如:
ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>();
这里就指定了ArrayList_DIY容器只能放Integer。其实它是伪泛型,关于伪泛型,大家想了解更多可以自行百度。
底层实现是用数组:
transient Object[] elementData;
这个Object数组就是用来装元素的。
构造函数
/**
* 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_DIY(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);
}
}
public ArrayList_DIY() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
类中有两个构造函数,第一个是有参数构造函数,形参是int类型,通过其new对象时该形参就是容器的容量;一个是无参构造函数,通过其new对象时,容器的容量为默认容量,默认容量是10,看下面的代码。
通过上面这两个构造函数new出来的容器,一开始都是空的,即没有任何元素。
默认容量:
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
DIY ArrayList功能解析
添加元素功能(add方法)
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
源码如上挺简单的,add方法有一个参数(即需要添加的元素),该参数的类型就是在实例化对象时指定的泛型。
elementData[size++] = e;这语句才是真正的添加元素,ensureCapacityInternal(size + 1);这语句主要作用是判断容器的容量是否够添加一个新元素,不够就扩容。通俗一点说就像我们拿箱子装东西一样,装东西很简单,放进去就可以了,但是有个前提条件就是箱子的容量可以装得下这个东西,如果装不下我们就扩大箱子。
看一下ensureCapacityInternal(int minCapacity)方法。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
先看一下ensureCapacityInternal方法的形参minCapacity,意思大概是添加这个元素需要最小的容量值,这个方法是在add方法里被调用的,add方法传了什么值给它呢,
是size + 1,那么size是多少呢?看下面:
private int size;
size在通过上面介绍的两个构造函数new对象的时候,是默认值0。
所以当我们要add元素时,容器中最小的容量应当为当前容器的size 再加1(即将新增的一个元素),才能新增元素,否则要扩容再新增元素。
ensureCapacityInternal(int minCapacity)方法再去调用ensureExplicitCapacity(calculateCapacity(elementData, minCapacity))方法,
ensureExplicitCapacity方法的实参是calculateCapacity(elementData, minCapacity)方法。
所以我们先看calculateCapacity(Object[] elementData, int minCapacity)方法。
calculateCapacity方法顾名思义就是比较新增一个元素需要的最小容量(minCapacity)和容器当前容量(),孰大孰小?
calculateCapacity方法代码如下:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
这个代码一点都不难理解。
首先会判断一下elementData 是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么呢?看下面
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
大概意思是啥呢?意思大概是有着默认容量的空的容器,其实是配合无参构造函数使用的。
无参构造函数:
public ArrayList_DIY() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
通过无参构造函数new对象
ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>();
你像上面这样new对象时,底层的elementData就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
回到判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA?
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
什么时候相等呢?很简单就是通过无参构造函数new对象且更要add第一个元素时是相等的,因为add的是第一个元素所以minCapacity必定是1。
其实只按照我们目前的代码的话,完全可以不用return Math.max(DEFAULT_CAPACITY, minCapacity);这个语句,直接返回DEFAULT_CAPACITY就OK,因为DEFAULT_CAPACITY必定大于minCapacity。但是为何比较呢?凭经验猜想因为后面还有构造函数用来new对象时就初始化很多值,这里暂时还没用到。
再来看一下ensureExplicitCapacity(int minCapacity)方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++;是用来记录modify的次数,忘记了它的作用了,看它的源码解释,太啰嗦了,可以复制到有道词典查看,这里省略。源码如下:
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
if (minCapacity - elementData.length > 0)这判断如果是true,就是说minCapacity(size[原有元素数量] + 1[新添加的1个元素])大于容器(elementData)的容量(length ),那肯定添加不成功,所以需要先扩容;如果是false,false的情况有一种是两者相减=0,这种情况就是容器可以装下且是最后一个(再来一个就得扩容)。
为true时,需要扩容。grow(minCapacity);这个语句就是扩容,参数是minCapacity。grow方法源码如下:
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
int oldCapacity = elementData.length;拿到当前容器的容量值,
int newCapacity = oldCapacity + (oldCapacity >> 1);新的容量为原来容量 + 原来容量右移1,关于位运算的左移和右移可以自行百度。位运算是最快的运算,因为机器记录的就是二进制码。
if (newCapacity - minCapacity < 0)得到新容量再与需要的最小容量作比较,小于0的话证明容量不够,则新容量等于需要的最小容量,即newCapacity = minCapacity;。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
上面这语句先不管。大概就是在容量很大时做一些措施。
elementData = Arrays.copyOf(elementData, newCapacity);这个语句才是真正的获取新容量的容器。但是它的hashcode是一样的,有点想不通,感觉是和本地方法copy有关。
再看看Arrays.copyOf方法:
/**
* Copies the specified array, truncating or padding with nulls (if necessary)
* so the copy has the specified length. For all indices that are
* valid in both the original array and the copy, the two arrays will
* contain identical values. For any indices that are valid in the
* copy but not the original, the copy will contain <tt>null</tt>.
* Such indices will exist if and only if the specified length
* is greater than that of the original array.
* The resulting array is of exactly the same class as the original array.
*
* @param <T> the class of the objects in the array
* @param original the array to be copied
* @param newLength the length of the copy to be returned
* @return a copy of the original array, truncated or padded with nulls
* to obtain the specified length
* @throws NegativeArraySizeException if <tt>newLength</tt> is negative
* @throws NullPointerException if <tt>original</tt> is null
* @since 1.6
*/
@SuppressWarnings("unchecked")
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
copyOf是重载函数,由少参数的重载函数调用多参数的重载函数,这个是经常用到的,真正的处理逻辑写在多参数的重载函数里。
下面就是真正的逻辑:
/**
* Copies the specified array, truncating or padding with nulls (if necessary)
* so the copy has the specified length. For all indices that are
* valid in both the original array and the copy, the two arrays will
* contain identical values. For any indices that are valid in the
* copy but not the original, the copy will contain <tt>null</tt>.
* Such indices will exist if and only if the specified length
* is greater than that of the original array.
* The resulting array is of the class <tt>newType</tt>.
*
* @param <U> the class of the objects in the original array
* @param <T> the class of the objects in the returned array
* @param original the array to be copied
* @param newLength the length of the copy to be returned
* @param newType the class of the copy to be returned
* @return a copy of the original array, truncated or padded with nulls
* to obtain the specified length
* @throws NegativeArraySizeException if <tt>newLength</tt> is negative
* @throws NullPointerException if <tt>original</tt> is null
* @throws ArrayStoreException if an element copied from
* <tt>original</tt> is not of a runtime type that can be stored in
* an array of class <tt>newType</tt>
* @since 1.6
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
注意System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));,调用本地方法arraycopy方法。Math.min(original.length, newLength)这语句就是保证不会出错,怎么说?它的作用就是比较original.length和newLength的大小返回小的,如果相等,返回的值就是相等的。如果旧容器的长度(100)大于新容器的新长度(90),那么新容器只能装90个,再往下装就溢出了,所以这个语句就是为了防止出错。一般情况,旧容器的长度小于新容器的新长度,这样才能把旧数据原封不动的放到新容器。
测试添加元素功能(测试add方法)
测试代码:
package spring.boot.study.javasourcecode;
public class ArrayListTest {
private final static boolean trace = true;
public static boolean isTrace() {
return trace;
}
public static void main(String[] args) {
ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>(); // 通过无参构造函数new对象,默认的容器容量是10
/**
* 测试往容器里添加元素数组的功能。
* 往容器添加11个元素,添加到第11个时,容器需要扩容。
* 因为是无参构造函数new对象,在添加第一个元素时也会进行扩容,扩容到默认的容器容量10
*/
for (int i = 0; i < 11; i++) {
arrayList1_DIY.add(i);
}
System.out.println("arrayList1_DIY size=" + arrayList1_DIY.size());
}
}
输出结果:
需要进行扩容操作!
旧容量=0
新容量=10
需要进行扩容操作!
旧容量=10
新容量=15
arrayList1_DIY size=11
从输出结果可以知道,容器进行了两次扩容,一次是在添加第一个元素时,另一次是在添加第11个元素时。
总结添加元素功能(总结add方法)
当往容器添加元素时,需要先判断容器当前的容量是否可以装下一个新的元素,可以就直接添加否则先扩容再添加。如果容量=0则先扩容到默认容量即10,后续扩容的新容量是当前容量值(X) + 当前容量值(X)右移1。
至此,add方法解读完成了。
获取元素功能(get方法)
get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
代码很简单,return elementData(index);这语句是真正获取容器里指定位置的元素,调用elementData方法获取元素,rangeCheck(index);这语句是用来干嘛呢?顾名思义就是范围检查的,检查是否超过容器的范围,事实上可有可无,Java本身机会抛出java.lang.ArrayIndexOutOfBoundsException异常,这里的作用是自定义ArrayIndexOutOfBoundsException异常信息。先看一下rangeCheck方法代码:
rangeCheck方法(数组下标是从0开始)
public void rangeCheck(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
outOfBoundsMsg方法
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
测试没有rangeCheck方法,Java本身也会抛ArrayIndexOutOfBoundsException异常。
Java自身ArrayIndexOutOfBoundsException异常信息
index为负数
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -19
index为整数且大于size - 1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 19
source code自定义的ArrayIndexOutOfBoundsException异常信息,报出来的信息更合适开发人员追踪。
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 19, Size: 11
再看看elementData方法
@SuppressWarnings("unchecked")
private E elementData(int index) {
return (E) elementData[index];
}
很简单就是从elementData数组获取指定位置为index的元素,再做类型转换,因为elementData数组是Object,转换为用户指定的泛型。
测试获取元素功能(测试get方法)
测试代码:
package spring.boot.study.javasourcecode;
public class ArrayListTest {
private final static boolean trace = true;
public static boolean isTrace() {
return trace;
}
public static void main(String[] args) {
ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>(); // 通过无参构造函数new对象,默认的容器容量是10
/**
* 测试往容器里添加元素数组的功能。
* 往容器添加11个元素,添加到第11个时,容器需要扩容。
* 因为是无参构造函数new对象,在添加第一个元素时也会进行扩容,扩容到默认的容器容量10
*/
for (int i = 0; i < 11; i++) {
arrayList1_DIY.add(i);
}
System.out.println("arrayList1_DIY size=" + arrayList1_DIY.size());
int index_1 = arrayList1_DIY.get(0);
System.out.println(index_1);
int index_2 = arrayList1_DIY.get(1);
System.out.println(index_2);
}
}
输出结果
需要进行扩容操作!
旧容量=0
新容量=10
需要进行扩容操作!
旧容量=10
新容量=15
arrayList1_DIY size=11
0
1
总结获取元素功能(总结get方法)
调用get方法时,会先判断index是否是正溢出范围的,如果是就返回自定义的异常信息(这个range检查可有可无,只是为了自定义异常信息),如果不是,就调用elementData方法,返回指定位置的元素并做类型转换。
移除元素功能(remove方法)
讲到remove功能,DIY ArrayList和测试类的代码有所修改,
有两种移除方式一种是移除指定位置的元素,一种是移除value=某个值的所有元素。
这里先将通过指定位置移除元素,即remove(int index)
如下:
DIY ArrayList代码
package spring.boot.study.javasourcecode;
import java.util.Arrays;
public class ArrayList_DIY<E> {
transient Object[] elementData;
transient int modCount = 0;
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private int size;
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 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_DIY(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);
}
}
public ArrayList_DIY() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}
public void rangeCheck(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
}
@SuppressWarnings("unchecked")
private E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public int size() {
return size;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) {
// FIXME
if (ArrayListTest.TRACE && ArrayListTest.EXPANSION_TRACE) {
System.out.println("需要进行扩容操作!");
}
grow(minCapacity);
}
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// FIXME
if (ArrayListTest.TRACE && ArrayListTest.EXPANSION_TRACE) {
System.out.println("旧容量=" + oldCapacity);
System.out.println("新容量=" + newCapacity);
}
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
// remove功能,移除指定位置的元素
public E remove(int index) {
// 先做范围检查,可有可无,在一定条件下抛自定义的异常信息,方便追踪
rangeCheck(index);
modCount++; // modify次数+1
E oldValue = (E) elementData[index];
/**
* 注意:下面说的”移动“,并非是真正的移动,其实用的是copy,copy之后把需要清空的位置清空,做到所谓的移动。
* 下面的FIXME标签代码就是测试。了解了size和容器容量的话,这个不难掌握。
*
* 移除指定位置的元素后,需要判断是否要移动其它元素,去填充数组。
* 比如:elementData数组的size是10,即有10个元素,移除的是数组最后一位元素,即第10位(index=9),
* 这样就不需要移动其它元素了,直接就是elementData[9]=null,size-1;
* 如果不是最后一位,就先移动index后的元素先位置都先前,即index后的元素统一左移,最后空出的就是elementData[9]
*/
int numMoved = size - index - 1; // 得到需要移动元素的个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// FIXME
if (ArrayListTest.TRACE && ArrayListTest.REMOVE_TRACE) {
size--; // 移除了一个size减少1
System.out.println("移动后,数组的最后一位的值是" + elementData[size]);
System.out.println("移动后,数组的最后倒数第二位的值是" + elementData[size - 1]);
System.out.println("移动后,数组的倒数第三位的值是" + elementData[size - 2]);
} else { // 不是测试,则用JDK源码
elementData[--size] = null; // clear to let GC do its work
}
return oldValue;
}
}
看看remove方法代码
// remove功能,移除指定位置的元素
public E remove(int index) {
// 先做范围检查,可有可无,在一定条件下抛自定义的异常信息,方便追踪
rangeCheck(index);
modCount++; // modify次数+1
E oldValue = (E) elementData[index];
/**
* 注意:下面说的”移动“,并非是真正的移动,其实用的是copy,copy之后把需要清空的位置清空,做到所谓的移动。
* 下面的FIXME标签代码就是测试。了解了size和容器容量的话,这个不难掌握。
*
* 移除指定位置的元素后,需要判断是否要移动其它元素,去填充数组。
* 比如:elementData数组的size是10,即有10个元素,移除的是数组最后一位元素,即第10位(index=9),
* 这样就不需要移动其它元素了,直接就是elementData[9]=null,size-1;
* 如果不是最后一位,就先移动index后的元素先位置都先前,即index后的元素统一左移,最后空出的就是elementData[9]
*/
int numMoved = size - index - 1; // 得到需要移动元素的个数
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// FIXME
if (ArrayListTest.TRACE && ArrayListTest.REMOVE_TRACE) {
size--; // 移除了一个size减少1
System.out.println("移动后,数组的最后一位的值是" + elementData[size]);
System.out.println("移动后,数组的最后倒数第二位的值是" + elementData[size - 1]);
System.out.println("移动后,数组的倒数第三位的值是" + elementData[size - 2]);
} else { // 不是测试,则用JDK源码
elementData[--size] = null; // clear to let GC do its work
}
return oldValue;
}
FIXME标签下的else是源码,加上FIXME是为了测试。
把下面代码替换就是源码
// FIXME
if (ArrayListTest.TRACE && ArrayListTest.REMOVE_TRACE) {
size--; // 移除了一个size减少1
System.out.println("移动后,数组的最后一位的值是" + elementData[size]);
System.out.println("移动后,素质的最后倒数第二位的值是" + elementData[size - 1]);
System.out.println("移动后,素质的倒数第三位的值是" + elementData[size - 2]);
} else { // 不是测试,则用JDK源码
elementData[--size] = null; // clear to let GC do its work
}
替换为:
elementData[--size] = null; // clear to let GC do its work
remove方法,remove元素时会判断移除的元素位置是不是最后一个,如果不是,若移除该元素,则该元素后面的其它元素都得往前填补位置。源码里是先“移动”元素即该元素后面的其它元素往前填补位置,其实这个时候该元素已经不存在数组了(大家不要钻牛角尖,因为List是可重复)。
需要注意的是,这里说的“移动”并非真正移动,而是用copy的方式,然后再清空最后一位重复的元素,就做到所谓的移动。需要理清size和容器容量的概念以及区别。
remove方法不是用移动,而是用copy
remove(int index),如果index不是最后一位,它会把index位置的后一位元素copy到index位置,后两位元素copy到后一位位置…,把循环如此直到数组最后一个元素。但是这样尾部就会重复,所以需要将尾部的值置为null,这样垃圾回收器就能回收了。
elementData[--size] = null; // clear to let GC do its work这语句就是上面的意思。
还是画个图吧,如下:
copy对应的代码部分
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
将ArrayListTest类中的REMOVE_TRACE设为false
public final static boolean REMOVE_TRACE = false;
测试移除指定位置元素功能(测试remove指定位置元素方法)
arrayList1_DIY size=11
移除元素前,index=7的值为:7
移除元素后,index=7的值为:8
总结移除指定位置元素功能(总结remove指定位置元素方法)
要了解size和容器容量的概念以及区别。
remove方法时通过copy指定index后元素放到前一位,直到最后一位元素完成copy。还要去掉最后一位元素,达到“移动”的效果。