JDK8 ArrayList源码解读
前言
利用下班时间在网上找了资料和JDK8的源码,对ArrayList和LinkedList的源码进行了研究,对自己的学习成果出作两篇博客;毕竟,好记性不如烂笔头啊!
1.概述
ArrayList是基于
[]
数组实现的,支持自动扩容的动态数组。相对来说,因为支持自动扩容的特性,成为我们日常开发中,最常用的集合类,没有之一。
2.类图
从图可以看到ArrayList实现了四个接口,和继承了一个抽象类
java.util.List
接口,提供数组的添加、删除、修改、迭代便利的过操作
java.util.RandomAccess
接口,表示ArrayList支持快速随机访问
java.io.Serializable
接口,表示ArrayList支持序列化的功能
java.lang.Cloneable
接口,表示ArrayList支持克隆
java.util.AbstractList
抽象类,AbstractList提供了List接口的骨架实现,大幅度的减少了实现迭代遍历相关操作的代码;比如抽象类中的iterator() 和 indexOf(Object o)等方法
3.属性
ArrayList的属性很少,仅仅只有2个,如图所示
elementData
属性:元素数组。其中,图中红色空格代表我们已经添加元素,白色空格表示我们并未使用
size
属性:数组大小。注意size
代表的是ArrayList已使用的elementData
的元素数量,对于开发者看到的size()
也是该大小。并且当我们添加新的元素时,恰好其就是元素添加到elementData
的位置(下标)。当然我们知道ArrayList真正的大小是elementData
的大小
对应的源码如下:
/**
* 元素数组
* 当添加新的元素时,如果该数组不够(长度),会创建新数组,并将原数组的元素拷贝到新数组。
* 之后,将该变量指向新数组
*
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access 不使用 private 修复,方便内嵌类的访问
/**
* 已使用的数组大小
*
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
4.构造方法
ArrayList一共有三个构造方法
1.#ArrayList(int initialCapacity)
#ArrayList(int initialCapacity)
构造方法,根据传入的初始化容量,创建ArrayList数组。如果我们在使用时,想要预先指定数组大小可以使用该构造方法,这样可以避免数组扩容,从而提升性能;同时也是合理使用内存
对应源码如下:
/**
* 共享的空数组对象
* 在 {@link #ArrayList(int)} 或 {@link #ArrayList(Collection)} 构造方法中,
* 如果传入的初始化大小或者集合大小为0时,将{@link #elementData} 指向它
* 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(int initialCapacity) {
// 初始化容量大于0时,创建Object数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
// 初始化容量等于0时,使用 EMPTY_ELEMENTDATA 对象
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
// 初始化容量小于0时,抛出IllegalArgumentException异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
比较特殊的是,如果初始化容量为0时,使用
EMPTY_ELEMENTDATA
空数组。在添加元素的时候,会进行扩容创建需要的数组
2.#ArrayList(Collection<? extends E> c)
#ArrayList(Collection<? extends E> c)
构造方法,使用传入的 c 集合,作为ArrayList的elementData
对应源码如下
/**
* 构造一个包含指定元素的列表
* 集合,它们按集合的返回顺序返回迭代器。
*
* 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) {
//集合中的toArray()方法 将 c 转换成Object 数组
elementData = c.toArray();
// 如果数组长度大于0
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//如果集合元素不是 Object[] 类型,则会创建新的Object[]数组,并将 elementData 赋值到其中,最后赋值给elementData
if (elementData.getClass() != Object[].class)
//Arrays.copyOf() 方法传回的数组是新的数组对象《改变传回的数组中的元素值,不会影响原数组》
elementData = Arrays.copyOf(elementData, size, Object[].class);
//如果数组大小等于0,则使用EMPTY_ELEMENTDATA
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
需要注意的是:在集合元素类型的判断,在JDK8中它其实是一个bug,在JDK9中这个问题被解决了
写一个测试类来说明一下
code
public class Test {
public static void main(String[] args) {
//使用数组工具了构建一个集合,集合的范型为Integer类型
List<Integer> list = Arrays.asList(1, 2, 3);
//调用集合中的toArray() 方法将集合转成一个数组
Object[] objects = list.toArray();
//objects 是数组对象的对象名,通过对象名.getClass() 方法获取类对象,并调用getSimpleName()查看对象的类型
System.out.println("arrayClassName:"+objects.getClass().getSimpleName());
//通过数组的索引改变对应索上的值
objects[0] = new Object();
}
}
结果说明:
测试结果:
1.通过类对象打印的对象类类型是 Integer[]类型
2.通过数组的索引改变对应索上的值
- 在JDK8中此处会抛出运行时异常
java.lang.ArrayStoreException: java.lang.Object
意思就是列表中储存的类型与列表的类型不一致- 在JDK9中这个问题已经被解决,也就是说在JDK9中这种写法是不会抛出运行时异常的
3.#ArrayList()
无参构造方法
#ArrayList()
构造方法,也是我们使用最多的构造方法
对应源码如下:
/**
* Default initial capacity.
* 默认初始化容量(大小)
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 共享的空数组对象,用于{@link #ArrayList()} 构造方法
* 通过使用该静态变量,和{@link #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 = {};
/**
* 构造一个初始容量为10的空列表
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
看完源码后,我们发现问题:
在未设置初始化容器时,ArrayList默认大小为10,但是此处,我们可以看到初始化为
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
这个空数组。这是为什么呢?原因是:
ArrayList考虑到节省内存,一些使用场景下仅仅是创建了ArrayList对象,实际上并没有使用。所以ArrayList优化成初始化是一个空的数组,在首次添加元素时,才真正初始化为容量为10的数组
那问题又来了,为什么单独声明了
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
空数组,而不直接使用EMPTY_ELEMENTDATA
呢?我们继续往下看,在下文中,我们会看到
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
首次扩容为10,而EMPTY_ELEMENTDATA
按照1.5倍扩容,从0开始而不是10;两者的起点不同
5.使用空参构造创建集合对象、向集合中添加单个元素
用这例子来说明集合中的数组初始化实际和向集合中添加单个元素底层实习过程
1.使用集合的空参构造创建集合对象
ArrayList<String> list = new ArrayList<>();
通过ArrayList类中的空参构造方法来构建对象,其原类的构造方法,与其相关属性如下
对应源码如下
/** * 共享的空数组对象,用于{@link #ArrayList()} 构造方法 * 通过使用该静态变量,和{@link #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 = {}; /** * 构造一个初始容量为10的空列表 * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
看源码我们可以看出来在使用空参构造方法创建对象时,空参构造方法elementData属性的值赋值成了一个空的数组(类型是
Object[]
),这就意味着被创建好的集合对象中的默认初始化数组长度为0
,size属性的初始化值为数据类型的默认值0
上面说到,使用空参构造方法创建对象,被创建好的集合对象中的数组的初始化长度为
0
,这样以来就和我们往常的认知不一样了,那ArrayList集合的数组默认值初始化大小为10
是哪里来的呢?莫慌,我们继续往下看!!
2.往集合中添加一个元素
#add(E e);
list.add("张三");
此时是调用了集合中的add(E e);方法,该方法的作用是用来往集合中添加单个元素。
底层看一下,在调用添加元素方法后,添加元素的过程中都做了些什么
对应源码如下
//执行步骤1 /** * 将指定的元素追加到此列表的末尾。 * 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(int minCapacity) //根据传入的数组大小(长度),对elementData进行初始化,或者扩容 ensureCapacityInternal(size + 1); // Increments modCount!! //将元素插入到列表的指定索引上 //size++; 是先赋值,后自增,第一次添加元素时size=0,第二次size=1....... elementData[size++] = e; return true; } //执行步骤2 /** * 根据传入的数组大小(长度),对elementData进行初始化,或者扩容 * @param minCapacity 数组大小(长度) */ private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //执行步骤3 /** * 获取元素数组【elementData】的容量(长度) * @param elementData 元素数组 * @param minCapacity 数组大小 * @return */ private static int calculateCapacity(Object[] elementData, int minCapacity) { //如果数组元素等于空,则返回元素数组的默认初始化大小 //否则返回传入的数组大小 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } //执行步骤4 /** * 1.记录数组被修改的次数 * 2.真实数组大小与元素数组的大小进行比较 * 如果真实数组的大小比元素数组的大小大 >> 调用扩容方法(第一次可以叫初始化) * 如果真实数组的大小比元素数组的大小小 >> 不做任何操作,方法结束;回到add(E e)方法进行元素的添加 * @param minCapacity 数组大小(可以理解为元素数组的真实使用大小) */ private void ensureExplicitCapacity(int minCapacity) { //父类(AbstractList)中的属性,用来记录数组被修改的次数 //数组修改次数加一 modCount++; // overflow-conscious code //数组大小(我们可以将它理解称elementData的实际使用长度) //如果数组大小减去元素数组的长度大于0,调用grow(int minCapacity)方法就像初始化,或者扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } //执行步骤5 /** * elementData扩容/初始化 * @param minCapacity */ private void grow(int minCapacity) { // overflow-conscious code //获取元素数组的长度(元素数组:elementData) int oldCapacity = elementData.length; //创建行的数组长度并扩容(元素数组扩容后的数组大小【长度】,原容量的1.5倍) int newCapacity = oldCapacity + (oldCapacity >> 1); //如果元素数组扩容后的大小小于传入数组大小,就将传入的数组大小的值赋值为元素数组 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // //判断元素数组大小是否如果超过规定的数组最大长度, //如果超过调用hugeCapacity(int minCapacity)方法对传入minCapacity值(数组大小)进行判断,是否为负数, //如果是抛出`内存不足错误`异常 //如果不是则返回最大Integer的最大取值 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //创建新的数组,并将 elementData 赋值到其中,最后赋值给elementData elementData = Arrays.copyOf(elementData, newCapacity); } //执行步骤6 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
总结一下这个过程:
1.在通过ArrayList类中的空参构造方法创建对象后,因为ArrayList集合考虑到节省内存,所以创建好的对象中的数组是一个空的数组(即初始化长度为0)
2.在第一次向集合中添加元素时(调用
add(E e)
),在添加元素前,他会先去调用一个方法calculateCapacity(Object[] elementData, int minCapacity)
,此时这个方法做的事情就是给elementData进行初始化长度,如果不是第一向集合中添加元素,添加前的第一步也是调用的这个方法,此时是根据方法的参数列表(传入的是size的值)去判断数组是否需要扩容3.添加元素
6.添加多个元素
#addAll(Collection<? extends E> c)
方法,批量添加多个元素,在我们明确知道会添加多个元素时,推荐使用该方法而不是添加单个元素,避免可能多次扩容
对应源码如下
//执行步骤1
/**
*将指定集合中的所有元素追加到的末尾
*这个列表,按它们被返回的顺序排列
*指定集合的迭代器。这个操作的行为是
*如果操作期间修改了指定的集合,则未定义
*正在进行中。(这意味着这个调用的行为是
*如果指定的集合是此列表,则为未定义的
*非空的列表)
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator. The behavior of this operation is
* undefined if the specified collection is modified while the operation
* is in progress. (This implies that the behavior of this call is
* undefined if the specified collection is this list, and this
* list is nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
//将传入的集合转成数组
Object[] a = c.toArray();
//获取数组的长度
int numNew = a.length;
//size + numNew >>> elementData数组中真实使用的长度 + 需要添加元素的长度
ensureCapacityInternal(size + numNew); // Increments modCount
//将a数组从0索引开始拷贝到elementData数组中,拷贝a数组个大小个长度
System.arraycopy(a, 0, elementData, size, numNew);
//计算并保存当前elementData数组实际使用长度
size += numNew;
//返回添加结果,传入的集合被转换成数组后的长度大于0则添加多个元素成功,否则添加不成功
return numNew != 0;
}
//执行步骤2
/**
* 根据传入的数组大小(长度),对elementData进行初始化,或者扩容
* @param minCapacity 数组大小(长度)
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//执行步骤3
/**
* 获取元素数组【elementData】的容量(长度)
* @param elementData 元素数组
* @param minCapacity 数组大小
* @return
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果数组元素等于空,则返回元素数组的默认初始化大小
//否则返回传入的数组大小
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//执行步骤4
/**
* 1.记录数组被修改的次数
* 2.真实数组大小与元素数组的大小进行比较
* 如果真实数组的大小比元素数组的大小大 >> 调用扩容方法(第一次可以叫初始化)
* 如果真实数组的大小比元素数组的大小小 >> 不做任何操作,方法结束;回到add(E e)方法进行元素的添加
* @param minCapacity 数组大小(可以理解为元素数组的真实使用大小)
*/
private void ensureExplicitCapacity(int minCapacity) {
//父类(AbstractList)中的属性,用来记录数组被修改的次数
//数组修改次数加一
modCount++;
// overflow-conscious code
//数组大小(我们可以将它理解成elementData的实际使用长度)
//如果数组大小减去元素数组的长度大于0,调用grow(int minCapacity)方法就像初始化,或者扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//执行步骤5
/**
* elementData扩容/初始化
* @param minCapacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
//获取元素数组的长度(元素数组:elementData)
int oldCapacity = elementData.length;
//创建行的数组长度并扩容(元素数组扩容后的数组大小【长度】,原容量的1.5倍)
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果元素数组扩容后的大小小于传入数组大小,就将传入的数组大小的值赋值为元素数组
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//
//判断元素数组大小是否如果超过规定的数组最大长度,
//如果超过调用hugeCapacity(int minCapacity)方法对传入minCapacity值(数组大小)进行判断,是否为负数,
//如果是抛出`内存不足错误`异常
//如果不是则返回最大Integer的最大取值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//创建新的数组,并将 elementData 赋值到其中,最后赋值给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
//执行步骤6
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
总结一下这个过程:
调用
addAll(Collection<? extends E> c)
向数组中添加多个元素,方法的参数列表是一个集合,这个方法要做的事情就是将这个集合中的数据按照顺序添加到目标集合中1.先将传入的集合转换成一个
Object[]
类型的数组2.获取数组的长度,与
size
相加;得到的结果为ensureCapacityInternal(int minCapacity)
方法的参数,这个方法做的事情就是判断ArrayList集合对象的中elementData是否需要初始化大小;需要的话就讲elementData的初始化大小返回;如果需要就讲传入的参数作为方法的返回体3.
2
返回的值做为ensureExplicitCapacity(int minCapacity)
方法的参数,这个方法就是看elementData是否需要扩容(根据传入的参数减去elementData的长度判断;结果>0?扩容:不扩容
)4.通过System类中的静态方法
arraycopy()
将需要添加的元素拷贝到elementData中
7.移除单个元素
#remove(int index)
方法,移除指定位置上的元素,并返回该位置的原元素
对应源码如下:
/**
*
* 移除列表中指定位置的元素。
* 将任何后续元素向左移动(从它们的元素中减去1指数)。
*
* 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) {
//判断方法传入的索引是否大于等于elementData的长度
//如果结果为true则抛出索引越界异常,false不处理
rangeCheck(index);
//父类(AbstractList)中的属性,用来记录数组被修改的次数
//数组修改次数加一
modCount++;
//根据索引从elementData中将对应的元素取出
E oldValue = elementData(index);
//判断要移除的元素是否在数组的末尾
//结果为true,则将该索引上的值重新定义为null `elementData[--size] = null;`
//如果为false,则调用System中的arraycopy通过拷贝的方式移除指定索引下的值
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 清除,让GC做它的工作
//返回被移除的元素值
return oldValue;
}
总结一下这个过程:
通过集合对象调用
remove(int index);
方法,将要移除的元素索引传入方法参数列表中1.在
remove(int index);
方法中,它会先去判断要移除的元素索引是否是在元素数组的范围内,如果不在就抛出索引越界异常
2.如果要移除的元素索引是在元素数组的范围内则代码进行往下运行,首先记录数组被修改的次数,根据元素的索引去elementData数组中获取元素的值,这个值一会做为
remove(int index);
方法的返回值3.判断要移除的元素是否在数组的末尾,如果是在数组的末尾就将该该索引的值重新赋值为null;如果不是末尾则通过拷贝的方式移除元素(其实就是将对应索引上的元素覆盖了)
#remove(Object o)
方法,移除首个为o
的元素,并返回是否移除成功(boolean)
对应源码如下:
/**
* 从列表中移除指定元素的第一个匹配项
*
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
//判断传入的`o`是为null(考虑数组中存在null值)
if (o == null) {
//为null
for (int index = 0; index < size; index++)
//判断,获取数组中第一个为null值的索引
if (elementData[index] == null) {
//调用`fastRemove`方法移除改索引上对应的值
fastRemove(index);
//返回移除结果
return true;
}
} else {
//不为null
for (int index = 0; index < size; index++)
//判断,获取数组中第一个为`o`的元素的缩影
if (o.equals(elementData[index])) {
//调用`fastRemove`方法移除改索引上对应的值
fastRemove(index);
//返回移除结果
return true;
}
}
//如果方法传入的元素在数组中并不存在就返回false
return false;
}
/*
* 移除指定索引上的值
* 私有移除跳过边界检查返回删除的值。
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
//父类(`AbstractList`)中的属性,用来记录数组被修改的次数
//数组修改次数加一
modCount++;
//判断要移除的元素索引是否为数组的最大缩影(数组中的最后一个值)
//结果为true,将该索引对应的值重新赋值为null
//结果为false,则通过`System.arraycopy()`拷贝的方式将指定索引的值移除(其实是被覆盖)
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 清除,让GC做它的工作
}
总结一下这个过程:
使用集合对象调用
remove(Object o);
方法后,传入需要被移除的值;这个方法它所做的事情就是移除数组中第一个为o
的值1.在
remove(Object o);
方法中,先回对方法的参数o
进行null
值判断,因为数组可能会存在null
值;如果
o
的值为null
,获取数组中第一个为null
值的索引,调用fastRemove(int index)
方法,将该索引作为方法的参数列表;在fastRemove(int index)
方法中,它首先做的事记录数组被修改的次数,其次就是判断要移除元素的索引是否为数组末尾元素,如果是直接将该索引下的值重新赋值为null;如果不是末尾则通过拷贝的方式移除元素(其实就是将对应索引上的元素覆盖了)。如果
o
的值不为null
,它的移除过程和为null
的移除过程是一致的,这里不在复述。
#removeRange(int fromIndex, int toIndex)
方法,批量移除[fromIndex,toTindex]
的多个元素注意:不包括
toIndex
的元素
对应源码如下:
/**
* 从该列表中删除索引位于之间的所有元素
* Removes from this list all of the elements whose index is between
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
* Shifts any succeeding elements to the left (reduces their index).
* This call shortens the list by {@code (toIndex - fromIndex)} elements.
* (If {@code toIndex==fromIndex}, this operation has no effect.)
*
* @throws IndexOutOfBoundsException if {@code fromIndex} or
* {@code toIndex} is out of range
* ({@code fromIndex < 0 ||
* fromIndex >= size() ||
* toIndex > size() ||
* toIndex < fromIndex})
*/
protected void removeRange(int fromIndex, int toIndex) {
//父类(AbstractList)中的属性,用来记录数组被修改的次数
//数组修改次数加一
modCount++;
//计算在这个数组中,toIndex索引后面还有多少个元素
int numMoved = size - toIndex;
//将需要移除的元素覆盖
/**
* 比方elementData数组中有这些元素 {1,2,3,4,5,null,null,null,null,null};对应的 size = 5;
* 现在我要移除数组中一个索引范围内的元素 【1~3】
* 那调用System.arraycopy方法对数组中的元素进行了覆盖,现在的elementData中的元素为{1,3,4,5,5,6}
* 接下来要做的就是将索引为4,5的元素值重新赋值为null即可
*/
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work 清除,让GC做它的工作
//计算需要移除的元素的索引
int newSize = size - (toIndex-fromIndex);
//循环变量数组,将对应的索引的元素值至为null
for (int i = newSize; i < size; i++) {
elementData[i] = null;
}
//将当前数组的长度赋值为size
size = newSize;
}
总结一下这个过程:
removeRange(int fromIndex, int toIndex);
该方法有两个参数,fromIndex
起使索引,toIndex
结束索引;这个方法的作用是移除从fromIndex
索引开始到toIndex
索引结束,这之间的元素(不包括toIndex
)根据源码我们可以看到,在执行过程中,它首先是记录了数组的修改次数,随后计算出了在结束索引后还有多少个元素,然后将要元素往前移动,这里其实是一个覆盖的过程;最后它将后面的元素的值至为
null
过程结束
其实你会发现在这个移除的过程中,elementData的大小(长度)没有发生改变,所谓的移除其实是一个修改的过程
8.查找单个元素
#indexOf(Object o)
方法,查找首个为指定元素的位置
对应源码如下:
/**
* 查询首个为指定元素的位置
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*/
public int indexOf(Object o) {
//判断传入的元素是否为null(因为在存储元素的elementData中可能会存在null值)
if (o == null) {
//传入的元素为null
//size为elementData数组中存在元素的个数(相当于长度)
//循环遍历,取到第一个为null的索引并返回
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//传入的元素不为null
//做法和传入的元素为null一样
//循环遍历,取到第一个为传入的元素的索引并返回
for (int i = 0; i < size; i++)
//值比较并返回索引
//传入的值可能是基本数据类型也有可能是引用数据类型(int/String)
//这里使用equals进行比较,如果传入的值的类型String,Spring类重写了Object类中的HashCode和equals方法比较的是重写后规则也就是值
//如果是基本数据类型那么equals方法中使用的 `==` 比较的就是基本类型的变量值
if (o.equals(elementData[i]))
return i;
}
//如果没有匹配到对应的值,返回-1
return -1;
}
总结一下这个过程:
indexOf(Object o)
方法是找查传入的参数在elementData数组第一次出现的索引并将其返回在这个方法中它主要做了两件事情
- null值判断
因为数组中可能会存在null值,遍历elementData数组,通过判断找到第一个null值所在的索引并将其返回
- 非null值判断
传入的值不是null值,遍历elementData数组,通过Object类中的
equals
方法判断,返回元素对应的索引如果方法传入的值在数组中不存在,则返回
-1
9.设置指定位置的元素
#set(int index,E element)
方法,设置指定位置的元素。
对应源码如下:
/**
* 将列表中指定位置的元素替换为指定的元素
*
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
//校验 index 不要大于等于 size(索引越界)
rangeCheck(index);
//获取index位置的原元素
E oldValue = elementData(index);
//修改index位置为新元素
elementData[index] = element;
//将被修改的元素返回
return oldValue;
}
10.转换成数组
#toArray()
方法,将 ArrayList 转换成[]
数组
对应源码如下:
/**
* 返回一个包含列表中所有元素的数组按适当的顺序(从第一个元素到最后一个元素)排列。
*
* Returns an array containing all of the elements in this list
* in proper sequence (from first to last element).
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this list. (In other words, this method must allocate
* a new array). The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this list in
* proper sequence
*/
public Object[] toArray() {
//拷贝原数组中的元素并返回(Arrays.copyOf 不仅仅只是拷贝数组中的元素,在拷贝元素时,会创建一个新的数组对象)
return Arrays.copyOf(elementData, size);
}
11.求Hash值
#hashCode()
方法,求ArrayList的哈希值这个方法是ArrayList的抽象父类
AbstractList
中的方法
对应源码如下:
/**
* 返回此列表的哈希码值。
*
* Returns the hash code value for this list.
*
* <p>This implementation uses exactly the code that is used to define the
* list hash function in the documentation for the {@link List#hashCode}
* method.
*
* @return the hash code value for this list
*/
public int hashCode() {
//初始hash值
int hashCode = 1;
//this表示当前对象(也就是当前调用hashCode方法的集合对象)
//遍历每一个元素 *31求hash
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
总结一下这个过程:
集合对象在调用hashCode方法后,在这个方法中有一个初始的hash值为
1
,如果当前集合长度不等于0
,遍历集合,求hash值;hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
如果元素的值不为null时元素基本数据类型调用它的包装类的hashCode,不是基本数据类型则调用本类中的hashCode方法计算当前元素的hash值;为null则hash值为0
ArrayList求Hash值为什么是乘以31?
之所以使用 31, 是因为他是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)。使用素数的好处并不很明显,但是习惯上使用素数来计算散列结果。 31 有个很好的性能,即用移位和减法来代替乘法,可以得到更好的性能: 31 * i == (i << 5)- i, 现代的 VM 可以自动完成这种优化。这个公式可以很简单的推导出来。
1.清空数组
#clear()
方法,清空数组
对应源码如下:
/**
* 清空数组
*
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
//父类(AbstractList)中的属性,用来记录数组被修改的次数
//数组修改次数加一
modCount++;
// clear to let GC do its work 清除,让GC做它的工作
// 遍历数组,倒序设置为 null
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}