ArrayList概述
本文讲述的是jdk1.8的ArrayList。ArrayList位于jdk的rt.jar中,java.util.ArrayList。
一个类中有以下内容:
继承、实现的类:说明了该类继承哪些特性。
属性:分为公有、私有、受保护的、默认。
方法:分为构造方法、其他方法,同样也分为公有、私有、受保护的、默认。
内部类:顾名思义,类里面的类,为何放在类里面呢,就是自己用呗。
我们分析ArrayList的源码,也是从类中的内容下手。
ArrayList的继承、实现关系
首先看ArrayList的继承、实现关系。
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
}
java.util.ArrayList继承java.util.AbstractList,实现java.util.List、java.util.RandomAccess、java.lang.Cloneable、java.io.Seriializable。
- java.util.AbstractList:它实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。一般这种抽象类中都会有部分的默认实现,ArrayList继承AbstractList以后,会继承部分功能,不需要实现。
- java.util.List:List接口,定义了数组队列的一些增删改查方法。其实ArrayList继承的AbstractList类已经实现了List接口,再次实现可能是便于阅读代码,历史版本迭代失误了。
- java.util.RandomAccess:标志接口,有点像现在的注解。接口内容空的,实现它的类具体“快速随机访问功能”,也就是ArrayList中的get方法。详情见我的博客《ArrayList源码之6260652 bug号》。
- java.lang.Cloneable:标志接口,实现该接口,就实现克隆功能,下面源码会介绍。
- java.io.Seriializable:标志接口,实现该接口,该对象就可以被序列化,用于网络传输或持久化存储。那网络传输完成或持久化完成后,就需要反序列化。
ArrayList中声明的变量、常量
为了便于下面讲解源码,对ArrayList中声明的属性做一个简单的介绍。为了防止误导别人,英文注释任然保留。
/**
* 序列化编号,因为实现类java.io.Serializable,ArrayList可以被序列化。
* 序列化与反序列化就用serialVersionUID来验证版本的一致性
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* Default initial capacity.
* ArrayList默认初始化大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 空数组实例,如果集合的大小为0,elementData就赋值为DEFAULT_CAPACITY
* 区别于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是默认构造方法时,数组的默认值
*/
private static final Object[] 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.
* 空数组实例,ArrayList默认构造方法中,不指定集合大小时,数组的默认值。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_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修饰表示该属性不参与序列化
* non-private to simplify nested class acces这段注释,有空研究
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
* 集合的大小
* 这个要区别于elementData.length。
* elementData.length表示的是集合有这么大地方,但可能没存满。
* size是集合中真正存储了多少个元素
*/
private int size;
ArrayList中的方法总结
构造方法
- public ArrayList()
- public ArrayList(Collection<? extends E> c)
- public ArrayList(int initialCapacity)
public、protected方法
public的方法,供ArrayList实例调用的方法,例如add、remove、set、get等。
add方法
- public boolean add(E e) :为集合在末尾添加指定元素e。
- public void add(int index, E element) :为集合在指定位置index添加指定元素 element。
- public boolean addAll(Collection<? extends E> c):为集合在末尾添加指定集合c中的元素。
- public boolean addAll(int index, Collection<? extends E> c) :为集合在指定位置index添加指定集合c。
remove、retainAll方法
- public E remove(int index):移除集合中指定位置index的元素。
- public boolean remove(Object o) :移除集合中指定元素o。
- public boolean removeAll(Collection<?> c):移除集合中指定集合中c中的元素。
- public boolean retainAll(Collection<?> c):和removeAll正好相反,保留了参数集合c中有的元素。
- public boolean removeIf(Predicate<? super E> filter):移除集合中符合filter过滤规则的元素。
- protected void removeRange(int fromIndex, int toIndex):移除集合中fromIndex~toIndex位置的元素。
set、replaceAll
- public E set(int index, E element):修改集合中指定位置index的元素element。
- public void replaceAll(UnaryOperator operator):按照指定操作规则,替换集合中的元素。与removeIf类似,可以用内部类,也可以用lamda表达式。
get方法
- public E get(int index):获取集合中指定位置index的元素。
clear方法
- public void clear() :清空集合中的所有元素。
clone方法
- public Object clone():克隆方法,克隆出一个新的ArrayList。
indexOf、lastIndexOf、contains、isEmpty、方法
- public int indexOf(Object o):返回元素o在集合中的脚标,否匹配到该元素,用.equals方法,如果没有该元素,则返回-1。
- public int lastIndexOf(Object o):区别于indexOf,lastIndexOf是从后往前遍历的,遇到元素则返回脚标。
- public boolean contains(Object o):集合中是否包含元素o,巧妙的复用了indexOf方法。indexOf方法返回-1,则表示不包含该元素,就返回false,否则返回true。
- public boolean isEmpty():集合是否为空,用size变量做的判断。
ensureCapacity方法
- public void ensureCapacity(int
minCapacity):扩容数组的方法,如果对集合大小有个期望,可以主动调用此方法,一次性将数组扩容到位,避免频繁扩容数组消耗性能。
ArrayList的构造方法
ArrayList有三个构造方法,如下文所示。分别是初始化指定大小的集合、初始化默认的集合、以指定集合为基础初始化一个集合。其实构造方法就是初始化集合中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);
}
}
从代码可以看到,初始化集合,就是初始化elementData 。elementData 数组是存储集合元素的数组,也就是说,集合元素都存储在elementData 中。
- 构造方法参数集合初始化大小如果为负数,抛异常;
- 如果大于0,初始化指定大小的数组;
- 如果为0,给定默认空数组EMPTY_ELEMENTDATA;
ArrayList()
源码如下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
很简单,初始化一个空的数组。
同样是空数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA区别于EMPTY_ELEMENTDATA。
ArrayList(Collection<? extends E> c)
源码如下:
ublic 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;
}
}
首先把构造方法传进来的集合转换为数组。
- 如果数组的长度不等于0,且数组的类型是Objectp[].class,直接将集合转化的数组赋值给本集合的数组elementData ;
- 如果数组的长度不等于0,且数组的类型不是Object[].class,将集合转化的数组转成Object[].class,这样就可以赋值给本集合的数组elementData。这么做的原因可参考我的博客《
ArrayList源码之6260652 bug号》; - 如果数组的长度等于0,把空数组EMPTY_ELEMENTDATA赋值给本集合的数组elementData ;
ArrayList的增删改查方法
ArrayList其实就是个数据结构,既然是数据结构,他的功能就是存储数据、获取存储的数据、修改存储的数据,也就是下面将要介绍的add、get、delete、set方法。
add方法
add方法是向集合中添加元素,jdk1.8中有如下四个方法:
- public boolean add(E e):向集合结尾追加元素e
- public void add(int index, E element):向集合指定位置index追加元素e
- public boolean addAll(Collection<? extends E> c):向结合结尾追加集合c
- public boolean addAll(int index, Collection<? extends E>
c):向集合指定位置index追加集合c
boolean add(E e)
该方法入参为将要添加到集合中的元素,返回结果为是否添加成功。
源码如下:
public boolean add(E e) {
//我需要size + 1这么大的数组存储数据,如果不够大,得扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//将元素e添加到集合的结尾
elementData[size++] = e;
//返回添加成功标识true
return true;
}
源码中的注释“// Increments modCount!!”参考博客文章《xxx》。
从源码中看到,表面只有三行代码。
- 想要向ArrayList中添加元素,首先得看看ArrayList的elementData数组够不够大,如果不够大需要扩容,这是第一行代码。ensureCapacityInternal(size + 1);
- 既然elementData数组地方够大了,可以存储下新添加的数据了,则把e元素存储在数组的末尾。这是第二行代码。elementData[size++] = e;
- 新添加的元素存储成功后,返回添加成功的标识。这是第三行代码。 return true;
下面的方法的含义是,我需要minCapacity这么大的数组,不满足大小则需要扩容。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
下面的代码含义是根据原数组元素及期望的最小数组长度,计算出期望的数组长度值。
计算的原则是,取默认数组长度10和期望最小数组长度的最大值。这样的意义是啥呢?下面会介绍。
/**
* 根据原数组元素及期望数组长度,计算期望数组的长度
* @param elementData 原集合中存储的所有元素
* @param minCapacity 期望数组长度的最小值
* @return 计算出的期望数组长度
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//默认无参数构造的ArrayList时
//如果期望数组长度小于DEFAULT_CAPACITY=10,则期望数组长度为DEFAULT_CAPACITY=10
//如果期望数组长度大于DEFAULT_CAPACITY=10,则期望数组长度为minCapacity
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果结合中存储的元素数组为DEFAULTCAPACITY_EMPTY_ELEMENTDATA,也就是new ArrayList()构造方法进来时
//DEFAULT_CAPACITY为数组默认长度,值为10
//期望数组长度大小为DEFAULT_CAPACITY与minCapacity的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//如果不是默认无参数构造的ArrayList时,期望数组长度为minCapacity
return minCapacity;
}
下面的代码是集合修改次数加一,扩容数组。
/**
* 集合修改的次数modCount+1
* @param minCapacity
*/
private void ensureExplicitCapacity(int minCapacity) {
//集合修改的次数加一
modCount++;
// overflow-conscious code 上面的注释说明考虑到了溢出情况而设计的代码
//
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这段代码是真正的扩容数组。
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 扩容数组
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
//原来数组的长度
int oldCapacity = elementData.length;
//新数组的长度=1.5*原数组长度,>>1右移1位数学意义上就是除以2^1
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);
}
下面分析数组拷贝的方法。该方法定义在java.util.Arrays中
/**
* 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
*/
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
最后用到了下面的代码,这个方法是native方法,用c写的,只看入参
- scr:原始数组
- srcPos:原始数组的脚标
- dest:目标数组
- destPos:目标数组的脚标
- length:拷贝数组长度
从原始数组src的第srcPos开始拷贝,拷贝length个元素到dest数组中,dest数组元素从第destPos开始放
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
总结:
- 默认无参构造实例的对象elementData是空数。
- 向集合中添加元素时,先计算出期望的最小数组长度minCapacity,minCapacity这个值最起码保证elementData能足够大来存储所有的集合元素。
- 向ArrayList中添加元素时,可能是一个个向里面添加,总不能一次次扩容吧,扩容数组是数组拷贝的过程,消耗性能。所以elementData刚实例化是空数组,长度为0,添加一个元素后,数组的期望长度为
DEFAULT_CAPACITY=10,根据期望长度,数组扩容到1.5*10=15 - 继续向集合中添加元素,如果集合元素大于DEFAULT_CAPACITY,期望数组长度变为minCapacity,数组扩容到1.5*minCapacity
- 继续添加元素,集合总得有个界限吧,集合的界限就体现在elementData的大小。elementData的最大长度MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8,因为部分虚拟机数组对象的头上是有一些信息的,所以-8。可是有的时候还要继续向ArrayList中添加元素,所以不得已数组长度取值Integer.MAX_VALUE。
- 到头了,不能再扩了。
public void add(int index, E element)
该方法是向ArrayList的指定位置添加元素element。
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*向集合中的指定位置index添加元素element
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//检查index是否越界,越界抛异常
rangeCheckForAdd(index);
//参考add(E element)方法。确保数组足够大,不够大则扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//index前面的数组元素不需要变动,index及其后面的元素往后挪一个位置。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将添加的元素放到指定位置
elementData[index] = element;
//集合大小加一
size++;
}
public boolean addAll(Collection<? extends E> c)
向ArrayList中添加集合c中的所有元素,且从后面追加。具体逻辑见源码中注释。
/**
* 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.)
*将集合中的所有元素添加到ArrayList的末尾
* @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;
//查看elementData是否满足期望值大小,不满足则扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//把集合c中的元素从第0个开始拷贝到elementData中,且从elementData的末尾开始放,拷贝c中的所有元素
System.arraycopy(a, 0, elementData, size, numNew);
//集合大小赋值
size += numNew;
//numNew!=0,说明c中有元素,有数据被成功添加到集合中
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c)
向集合指定index位置添加集合c,具体逻辑见源码中注释。
{
//检查脚标index是否越界,越界则抛异常
rangeCheckForAdd(index);
//将集合转为数组
Object[] a = c.toArray();
//参数集合c的大小
int numNew = a.length;
//检查elementData是否足够大,不够大则扩容
ensureCapacityInternal(size + numNew); // Increments modCount
/**
* 原集合的大小-新集合将要加入的位置=需要往后挪的元素的个数numMoved
* elementData数组中index前面的元素保持不变,index位置的元素及后面的元素向后挪numNew个位置
*/
int numMoved = size - index;
if (numMoved > 0)
//elementData数组中index前面的元素保持不变,index位置的元素及后面的元素向后挪numNew个位置
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//将新集合c中的元素拷贝到elementData的index~numNew位置
System.arraycopy(a, 0, elementData, index, numNew);
//集合大小赋值
size += numNew;
//numNew!=0,说明c中有元素,有数据被成功添加到集合中
return numNew != 0;
}
remove方法
remove方法是将集合中的元素移除的方法。
public E remove(int index)
移除集合中指定位置的元素,源码及说明如下:
/**
* 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) {
//检查index是否越界,越界则抛异常
rangeCheck(index);
//集合的修改次数加1
modCount++;
//将要被移除掉的元素
E oldValue = elementData(index);
//需要挪动元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将集合中index位置后面的元素向左移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将集合中指定位置的元素置null,让GC工作
elementData[--size] = null; // clear to let GC do its work
//返回将要被移除掉的元素的值
return oldValue;
}
- rangeCheck(int index)方法说明
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
* 检测脚标是否越界,越界则抛异常
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public boolean remove(Object o)
移除集合中的o元素,具体逻辑见源码
/**
* 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).
*移除集合中的指定元素。如何判断是否为指定的元素呢,用.equals方法,所以记得重写对象的.equals方法
* @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) {
if (o == null) {//将要被移除的元素为null
//遍历elementData数组,遇到为null的元素,则移除,而且直接返回
//意味着只能移除遇到的第一个为null的元素
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
* 私有的移除元素方法,不用校验index是否越界,不需要返回被移除的元素值。
* 除了这两条,和remove(int index)方法一样
*/
fastRemove(index);
return true;
}
} else {
//遍历elementData数组,遇到为o的元素,则移除,而且直接返回
//意味着只能移除遇到的第一个为null的元素
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
* 私有的移除元素方法,不用校验index是否越界,不需要返回被移除的元素值。
* 除了这两条,和remove(int index)方法一样
*/
fastRemove(index);
return true;
}
}
//没找到将要被移除的元素,也就是移除失败了呗,就返回false
return false;
}
- fastRemove(index)方法说明
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
* 私有的移除元素方法,不用校验index是否越界,不需要返回被移除的元素值。
* 除了这两条,和remove(int index)方法一样
*/
private void fastRemove(int index) {
//集合修改的次数加一
modCount++;
//将要被挪动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//将集合中index位置后面的元素向左移动一位
System.arraycopy(elementData, index+1, elementData, index,
//返回将要被移除掉的元素的值 numMoved);
elementData[--size] = null; // clear to let GC do its work
}
public boolean removeAll(Collection<?> c)
移除ArrayList集合中,c集合中的元素,说明见源码:
public boolean removeAll(Collection<?> c) {
//判断集合c是否为空,为空则抛异常。平时可以考虑使用的工具类
Objects.requireNonNull(c);
//如果complement为true,保留c中不存在的元素;如果complement为false,保留c中存在的元素
return batchRemove(c, false);
}
- batchRemove(Collection<?> c, boolean complement)方法说明
/**
* 如果complement为true,保留c中不存在的元素;如果complement为false,保留c中存在的元素
* @param c
* @param complement
* @return
*/
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
//原数组的脚标计数r、新数组的脚标计数w
int r = 0, w = 0;
//是否被修改,也就是是否有元素被真正移除
boolean modified = false;
try {
/**
* 遍历ArrayList中的元素,即elementData中的元素
* 如果参数集合c中“存在该元素/不存在该元素”(与complement参数有关),则将该值放到elementData数组中,也就是保留在集合中
*/
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.
/**
* r!=size,意味着遍历elementData的过程中抛出异常,意外中断。
* 所以怎么办呢?需要将剩下的没遍历的元素拷贝进去,进哪里呢,其实就是继续保留在ArrayList中
*/
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
/**
* w!=size 意味着啥呢?意味着遍历elementData数组处理数据的过程中,有元素没有被保留,也就是相当于被删除了
* 那elementData数组中就该有空位置,就该被置空,让GC回收
* modCount怎么计数的呢,删一个元素+1,加一个元素+1,加两个元素+2
*/
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;
}
protected void removeRange(int fromIndex, int toIndex)
除集合中fromIndex~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.)
*移除集合中fromIndex~toIndex 的元素
* @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) {
//修改次数+1
modCount++;
//需要挪动的元素的个数
int numMoved = size - toIndex;
//把toIndex~toIndex+numMoved的元素向左移动numMoved个位置
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);
// clear to let GC do its work
//新集合的大小
int newSize = size - (toIndex-fromIndex);
for (int i = newSize; i < size; i++) {
//空的位置置空,方便GC
elementData[i] = null;
}
size = newSize;
}
public boolean removeIf(Predicate<? super E> filter)
按filter的过滤规则,移除掉集合中符合filter规则的元素,具体逻辑见源码及备注
/**
* 按filter的过滤规则,移除掉集合中符合filter规则的元素
* @param filter 移除元素满足的规则
* @return 是否有元素移除成功
*/
@Override
public boolean removeIf(Predicate<? super E> filter) {
//判断规则参数是否为空,为空则抛出异常
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
//声明移除掉的元素个数,默认为0
int removeCount = 0;
//移除掉的元素的脚标,位图需要学习
final BitSet removeSet = new BitSet(size);
//声明期望的集合修改次数,为final
final int expectedModCount = modCount;
//声明集合的大小,size为final
final int size = this.size;
//modCount == expectedModCount这句话是检测并发情况的
/**
* 该段for循环的意义是如果符合过滤规则,则将脚标存入到removeSet中,并且removeCount计数加一
*/
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
//检测并发情况
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
//是否有元素需要移除
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
//新集合的大小
final int newSize = size - removeCount;
/**
* 给新集合的元素赋值
*/
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
//elementData中没有元素的位置置空,触发GC
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
//集合大小赋值
this.size = newSize;
//检测并发情况,有则抛异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
//集合修改次数加一
modCount++;
}
//是否有元素移除成功
return anyToRemove;
}
set方法
set方法就是修改集合中的元素
public E 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是否越界,越界则抛异常
rangeCheck(index);
//将要被修改的值
E oldValue = elementData(index);
//修改值
elementData[index] = element;
返回旧值
return oldValue;
}
get 方法
获取元素,也就是查
public E get(int index)
获取集合中指定位置的元素,具体逻辑见源码和注释。
/**
* Returns the element at the specified position in this list.
*获取集合中指定位置的元素
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
//检测脚标是否越界,越界则抛异常
rangeCheck(index);
//获取指定位置的元素
return elementData(index);
}
ArrayList简单总结
- ArrayList存储数据,底层是数组。
- ArrayList插入数据时需要扩容数组,因此尽量初始化数组的大小。
- 添加、删除数据时需要移动数组中的元素,因此性能较差。
- 获取数据时,可以根据数组脚标直接找到数据,因此较快。
- ArrayList集合是有序的,因为底层是数组。
- ArrayList允许插入空值,元素可以重复。
- ArrayList线程不安全。
- List list= Collections.synchronizedList(new ArrayList<>());会让ArrayList变成线程安全的。