ArrayList类中的方法与实现原理
目录
- 五.其他方法
- public boolean add(Object o)
- public void add(int index,Object element)
- .public boolean addAll(Collection c)
- public boolean addAll(int index,Collection c)
- public void clear()
- public Object clone()
- .public boolean contains(Object elem)
- public boolean containsAll(Collection c)
- public boolean equals(Object o)
- public Object get(int index)
- public int hashCode()
- public int indexOf(Object elem)
- public boolean isEmpty()
- public Iterator iterator()
- public int lastindexOf(Object elem)
- public ListIterator listIterator()
- public ListIterator listIterator(final int index)
- public boolean remove(Object o)
- public Object remove(int index)
- public boolean removeAll(Collection c)
- public boolean retainAll(Collection c)
- public Object set(int index,Object element)
- public int size()
- public List subList(int fromIndex,int toIndex)
- public Object[] toArray()
- public Object[] toArray(Object []a)
- public String toString()
- public void trimToSize()
一.数据结构
ArrayList的底层数据结构就是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是就与数组的。对ArrayList进行添加元素的操作的时候是分两个步骤进行的,即第一步先将Object[size]的位置上存放需要添加的元素;第二步将size的值加1。由于这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。
如果非要在多线程的环境下使用ArrayList,就需要保证它的线程安全性,通常有两种解决办法:第一,使用synchronized关键字;第二,可以用Collections类中的静态方法synchronizedList();对ArrayList进行调用。
二.类标题
ArrayList类的标题如下:public class ArrayList extends AbstractList implements List,Cloneable,java.io.Serializable
这个标题说明ArrayList类是AbstractList类的子类,并且实现了三个接口:List、Cloneable和Serializable。如下图所示:
[Q1]为什么ArrayList继承了AbstractList还要实现List接口
Serializable接口源码:
package java.io;
public interface Serializable
{
}
凡是实现Serializable接口(java.io)的类都可以进行序列化和反序列化操作
[Q2]:Serializable为什么要定义一个空接口来进行标识
[Q3]:如何理解: implements Cloneable表示该对象能被克隆,能使用Object.clone()方法。如果没有implements Cloneable的类调用Object.clone()方法就会抛出CloneNotSupportedException。
三.字段
private transient Object elementData[];
private int size;//数组中元素的个数
protected transient int modCount = 0; //继承自AbstractList
【注】:成员变量modCount记录着集合的修改次数,也就是每次add或者remove它的值都会加1
四.构造函数
1.public ArrayList(int initialCapacity);作用:ArrayList类的构造方法,根据传入的initialCapacity值创建一个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);
}
}
源码解析:
传入initialCapacity为0,创建空数组,否则,创建intialCapacity大小的数组elementData
2.public ArrayList();
作用:ArrayList类的构造方法,创建一个空的数组。在第一次添加元素时容量扩大到10
源码如下:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
源码分析:创建一个空数组
3.public ArrayList(Collection c);
作用:使用指定Collection来构造ArrayList的构造函数
源码如下:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将传入的集合转换为一个数组,并将数组的应用赋给elementData
if ((size = elementData.length) != 0) {//数组不为空
// defend against c.toArray (incorrectly) not returning Object[]
if (elementData.getClass() != Object[].class)//数组不为Object[],将其复制成为Object
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {//数组为空的情况
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
分析:
1.将传入的集合转换为数组
2.判断转换后的数组是否为空
若为空:创建一个空的elementData数组
若不为空,且转换后的数组类型不为Object[]类型,将其复制为符合Object[]数组类型的elementData数组
五.其他方法
1.public boolean add(Object o);作用:将指定元素追加到列表的末尾。返回值是true
源码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判断是否需要扩容
elementData[size++] = e; //将元素插入到已有元素后一个位置
return true;
}
//将预计插入元素后所需最小容量传入EnsureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果调用的无参构造方法(创建空数组),minCapacity=10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果传入的预计插入后的最小容量大于当前数组的容量,用grow进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//对elementData[]进行扩容,每次增加1/2 elementData数组长度。
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:
// 调用 Arrays.copyOf 方法将 elementData 数组指向新的内存空间 newCapacity 的连续空间
// 并将 elementData 的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
源码分析:
ArrayList集合add()过程如下图所示:
[Q4]add(E e)方法为什么总是返回true?
不理解:[此方法总是返回true,是因为在Collection接口中对应的方法返回的是一个boolean值:如果调用对象所属类允许重复,或者被添加的元素尚不再调用对象中,就返回true,否则返回false.有些Collections接口的实现就不允许重复]
【注】Max_ARRAY_SIZE定义为private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8 其中,Integer.MAX_VALUE是Integer类中的一个int类型的常量MAX_VALUE,它代表int所能表示的最大值Ox7FFFFFFF
2.public void add(int index,Object element);
作用:将指定元素插入此列表的指定位置。将当前位置的元素(如果有)和任意后续元素向右移动。
源码如下:
//elementData数组在索引index处插入元素
public void add(int index, E element) {
rangeCheckForAdd(index);//检查index索引是否越界
ensureCapacityInternal(size + 1); //扩容,源码在1.public boolean add(Object o)c处
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//java.lang.System
/*
@Function:复制数组,以插入元素,但是要将index之后的元素都往后移一位。然后就是插入元素,增加sized的值
@src:源数组 srcPos:源数组要复制的起始位置 dest:目的数组 destPos:目的数组放置的起始位置 length:复制的长度
*/
public static native void arraycopy(Object src,int srcPos,Object dest, int destPosint length);
源码分析:
1.边界检查。检查传入的index索引是否越界。
2.容量判断。判断数组是否有足够空间接收元素,不够的话进行扩容操作。
3.右移。对索引后的元素进行右移一位操作。
4.插入元素。将新元素插入到数组索引处。
5.记录数组元素个数的size+1.
System.arraycopy(elementData, index, elementData, index + 1, size - index);
复制源数组elementData从index处开始size-index长度(复制index后面的元素)到elementData数组的index+1位置。图示如下:
[Q5]native关键字的使用
3.public boolean addAll(Collection c);
作用:将指定集合中的所有元素插入此列表末尾
源码如下:
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 同4.public boolean add(Object o)分析,确保数组容量足够
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
源码分析:
1.将集合转换为数组。
2.确保加入新数组元素后,数组容量足够
3.将转换后的数组添加到已有数组末尾
4.记录数组元素个数的size+numNew
System.arraycopy(a, 0, elementData, size, numNew);
复制a数组从索引0-numNew的元素到elementData数组的size位置。源码及分析在2.public void add(int index,Object element)处;
4.public boolean addAll(int index,Collection c);
作用:从指定位置开始,将指定集合中的所有元素插入此列表。将当前位置的元素元素往后移。参数列表中两个参数,第一个参数,填写数组下标,第二个参数,直接填写要被插入的数组名即可。
源码如下:
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);//索引边界检查,源码在);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // 确保容量足够,源码在1.public boolean add(Object o);
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);//源码在2.public void add(int index,Object element)
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
源码分析:
1.索引边界检查
2.将要插入的集合转换为数组
3.确保加入新数组元素后,数组容量足够
4.计算已有数组在index后的元素个数
5.将已有elementData数组index后的numMoved位元素右移numNew位
6.将常茹为numNew位的新数组插入到index的位置
7.记录elementData数组元素个数的size+numNew
5.public void clear();
作用:从此列表中删除所有元素。
源码如下:
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
源码分析:
成员变量modCount记录集合的修改次数。clear()方法原理即将elementData数组的所有元素赋空值。
6.public Object clone();
作用:返回此ArrayList实例的浅表副本。若要赋值给一个对象,需要强制转型。
源码如下:
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);
}
}
//Arrays.copyOf().Arrays的copyOf()方法传回的数组是新的数组对象,改变传回数组中的元素值,不会影响原来的数组。copyOf()的第二个自变量指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值
public static byte[] copyOf(byte[] original, int newLength) {//original 需要复制的源数组
byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
//from java.lang.Object
protected native Object clone() throws CloneNotSupportedException;
浅克隆:将新集合每个元素的引用指向原集合对应对象在栈空间的内存地址,所以,原集合修改或删除,克隆的集合随之改变;新集合修改添加会改变引用重新指向其他堆内存地址,删除就直接删除引用。
深克隆:将新集合的元素对象复制,在堆内存中重新开辟空间存一样的内容,一般要对集合中的对象重写clone(),在clone()中返回new的新对象,再add到新集合中,所以新旧集合操作互不影响。
源码分析:
ArrayList中的clone方法继承自Object类。ArrayList实现的clone()为浅克隆,即复制对象的引用。
1.利用Object类中的clone(),复制elementData数组的引用v
2.将已有数组elementData数组元素复制给v指向的elementData数组
[Q6]super.clone()进行了什么操作,v.elementData表示什么?
7.public boolean contains(Object elem);
作用:如果此列表中包含此元素,则返回true
源码如下:
public boolean contains(Object o) {
return indexOf(o) >= 0; //indexOf(Object o)方法同15.public int indexOf(Object elem);
}
源码分析:
查找elementData数组中是否包含o元素。
若包含,返回元素所在数组单元的索引值;
若不包含,返回-1.
8.public boolean containsAll(Collection c);
作用:方法用于检测 arraylist 是否包含指定集合中的所有元素。
源码如下:
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))//源码在7.public boolean contains(Object elem);
return false;
return true;
}
源码分析:
ArrayList类的containsAll(Collection c)继承自AbstractCollection类。
该方法用for-each遍历c集合,同时查看c集合中是否包含e元素。
若包含,则返回ture.否则返回false.
9.public boolean equals(Object o);
作用:这里调用的equals方法并不是Object类中的。ArrayList继承了AbstractList< E>,它的equals方法应该从这个抽象类中来的。发现该类确实重写了equals方法,就是使用迭代器遍历两个List每个元素是否相等。 那么ArrayList调用equals方法就是元素进行比较了。
源码如下:
//inheried from java.util.AbstractList
public boolean equals(Object o) {
if (o == this)//如果o为调用此方法的对象,则返回true
return true;
if (!(o instanceof List))//若对象o不是List对象,则返回false
return false;
//定义两个List迭代器用来遍历元素
ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
while (e1.hasNext() && e2.hasNext()) {//当两个迭代器均有下一个元素
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
源码分析:
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
若o1为空,返回o2 == null.则变为if(!(o2==null));此时,若o2不为null,则直接返回false.
若o1非空,返回o1.equals(o2).则变为if(!(o1.equals(o2)))【注】此处的equals()方法应为Object类中的equals()方法.若o1与o2不等,则返回false.
return !(e1.hasNext() || e2.hasNext());
若e1和e2都遍历完了,未返回false,则认定equals()返回true.
10.public Object get(int index);
作用:返回此列表中指定位置的元素
源码如下:
public E get(int index) {
rangeCheck(index);//检查索引是否越界,源码在2.public void add(int index,Object element)处
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
源码分析:
1.检查索引是否越界
2.返回elementData数组指定索引index位置的元素。
11.public int hashCode();
作用:求ArrayList的哈希值,这个方法是ArrayList的抽象父类AbstractList中的方法
源码如下:
//inherited from java.util.AbstractList
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
源码分析:
在JAVA语言中,判断两个对象是否相等,一般有两种方法,一种是hashcode(),另一种是equals(),这两个方法在判断准确性和效率上有很大的区别。hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢?
因为重写的equal()里一般比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?
因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠。
12.public int indexOf(Object elem);
作用:返回此列表中元素第一次出现的位置下标,如果没有此元素则返回-1
源码如下:
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
源码分析:
ArrayList类中indexOf(Object elem)重写了AbstractList类中的方法。
1.判断传入元素是否为空
若为空,遍历数组元素,用“==”找出数组单元元素为空所在的索引
若不为空,遍历数组元素,用“equals()”找出数组中与传入元素内容相同的单元所在的索引
否则,返回-1。
13.public boolean isEmpty();
作用:判断此列表中是否为空,空则返回true,否则返回false
源码如下:
public boolean isEmpty() {
return size == 0;
}
源码分析:
如果记录数组元素个数的size==0,表示数组中没有元素,所以返回true.否则,返回false.
14.public Iterator iterator();
作用:返回当前集合的双向迭代器
每个实现Iterable接口的类必须提供一个iterator方法,返回一个Iterator对象,ArrayList也不例外
源码如下:
public Iterator<E> iterator() {
return new Itr();
}
返回的是一个Itr类的对象,接下来我们来看它的源码
private class Itr implements Iterator<E> {
int cursor = 0;//指向下一个要被迭代的元素
int lastRet = -1;//指向当前元素
int expectedModCount = modCount;//在迭代器初始化过程中会将modCount赋给迭代器的expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示通过其他方法修改了 ArrayList的结构
public boolean hasNext() {
return cursor != size();
}
public E next() {//将cursor指向下一个被迭代的元素,lastRet指向当前元素
checkForComodification();//检查是否有线程在修改数组结构
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);//删除lastRet指向的数组单元
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
//有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。
-线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,
同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。 线程A继续遍历执行next方法时,
通告checkForComodification方法发现expectedModCount = N , 而modCount = N + 1,两者不等
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码分析:
每个实现Iterable接口的类必须提供一个iterator方法,返回一个Iterator对象,ArrayList也不例外
cursor--;
expectedModCount = modCount;
如图所示,Cursor初始状态是指向5,即若调用ArrayList的迭代器使用next()遍历数组,在遍历途中使用ArrayList.remove(1),则会跳原本应该遍历的5,直接遍历到6.采用上述代码后,它在每一次删除之后都会将cursor(下一项)的位置设置为当前位置,也就是将cursor往前移动了一位,之后再将modCount赋值给expectedModCount使它们保持相等。
15.public int lastindexOf(Object elem);
作用:返回此列表中元素最后一次出现的位置下标,如果没有则返回-1
源码如下:
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
源码分析:
若查找数组中指定元素最后一次出现的位置下标,从后往前遍历elementData数组,找到该元素第一次出现位置的索引值。
16.public ListIterator listIterator();
作用:从0开始,按当前集合中现有的元素进行迭代(双向迭代)
源码如下:
//inherited from java.util.AbstractList
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index);//检查索引是否越界
return new ListItr(index);//返回ListIter对象
}
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;//cursor表示当前元素索引
}
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {//返回当前元素的前一个元素
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {//返回当前元素后一个元素的索引值
return cursor;
}
public int previousIndex() {//返回当前元素前一个元素的索引值
return cursor-1;
}
public void set(E e) {//修改当前对象的属性
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {//向数组中添加对象
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1; //分析类似17.public Iterator iterator();
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
源码分析:
Iterator与ListIterator代码结构大同小异。其主要区别体现在以下:
(1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能
(2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
(3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
(4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
17.public ListIterator listIterator(final int index);
作用:返回指定下标(包含该下标)后的值
源码如下:
public ListIterator<E> listIterator(final int index) {
checkForComodification();//判断是否有线程修改数组
rangeCheckForAdd(index);//判断索引是否越界
final int offset = this.offset;
return new ListIterator<E>() {
int cursor = index;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount;
public boolean hasNext() {//是否存在下一元素
return cursor != SubList.this.size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= SubList.this.size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[offset + (lastRet = i)];
}
public boolean hasPrevious() {
return cursor != 0;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[offset + (lastRet = i)];
}
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = SubList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (offset + i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[offset + (i++)]);
}
// update once at end of iteration to reduce heap write traffic
lastRet = cursor = i;
checkForComodification();
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
SubList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(offset + lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
SubList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
};
}
源码分析:
18.public boolean remove(Object o);
作用:移除指定元素
源码如下:
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;
}
//快速删除es数组的第i个元素
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
19.public Object remove(int index);
作用:删除指定位置的元素,后续元素往前补
源码如下:
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
20.public boolean removeAll(Collection c);
作用:删除指定集合在此列表中的所有元素
源码如下:
//inherited from AbstractCollection
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//判断要移除的集合是否为空
return batchRemove(c, false);
}
/*
@parameter:Collection c:目标集合
boolean complement:当前集合是否需要包含目标集合
false,则删除目标集合在当前集合中所存在的元素
true,则删除目标集合在当前集合中不纯在的元素
*/
private boolean batchRemove(Collection<?> c, boolean complement)//
final Object[] elementData = this.elementData;//定义当前数组对象
int r = 0, w = 0;
// r 在外层定义for循环所用下标,目的是为了判断有没有报错,如果报错了,r就!=size,如果没有报错r==size
// w 进行操作的次数
boolean modified = false;//定义成功标识
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)//目标集合中元素存在或不存在 在当前集合中
elementData[w++] = elementData[r];//将不需要移除的元素放入elementData数组中
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {//表示报错了,与ArrayList的父类保持一致,当c.contains()这个方法抛出异常才会r != size
System.arraycopy(elementData, r,
elementData, w,
size - r);//复制数组,从报错下标开始复制到 w下标(最后被修改的下标),复制长度是未成功循环的长度
w += size - r;//因为复制后数组长度就变了,所以需要求出目前数组的长度,w+ 复制的长度
}
if (w != size) {// 表示原数组有删除了数据
// clear to let GC do its work
for (int i = w; i < size; i++)
//从w开始循环,循环到size,这些数据是要删除的数据,设为null
elementData[i] = null;
modCount += size - w;//修改操作次数计数 size-w表示删除的数量
size = w; //将长度设为新的长度
modified = true;
}
}
return modified;
}
源码分析:
1.判断要移除的集合是否为null,如果为空则抛出null异常
2.如果不为null,则调用batchRemove方法即可移除成功
2.1. 通过contains方法,将所有不需要移除的元素放入elementData数组中,然后标记最后一个放入元素的位置w
2.2. 将所有需要移除的元素设置为null
2.3. 将size的大小设置为w
2.4. 修改modCount字段(该阻断主要是为了防止多线程模式下安全问题)
2.5. 返回移除成功的标识
21.public boolean retainAll(Collection c);
作用:原数组和参数数组做交集,保留相同的部分。
源码如下:
//inherited from AbstractCollection
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);//源码在20.public boolean removeAll(Collection c)处
}
源码分析:
ArrayList类中的retainAll(Collection c)重写了AbstractCollection中的此方法
private boolean batchRemove(Collection<?> c, boolean complement)部分源码:
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)//目标集合中元素存在或不存在 在当前集合中
elementData[w++] = elementData[r];//将不需要移除的元素放入elementData数组中
removeAll()传入batchRemove中complement为false,表示将不存在的元素放入elementData数组
retainAll()传入batchRemove中complement为true,表示将存在的元素放入elementData数组中
22.public Object set(int index,Object element);
作用:用指定元素替换指定位置的元素。
源码如下:
public E set(int index, E element) {
rangeCheck(index);//检查索引是否越界
E oldValue = elementData(index);//将需要修改位置的值赋给oldValue
elementData[index] = element;//将element覆盖初值
return oldValue;
}
23.public int size();
作用:返回此列表中的元素数
源码如下:
public int size() {
return size;
}
24.public List subList(int fromIndex,int toIndex);
作用:只返回从fromIndex到toIndex之间的数据元素。
源码如下:
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);//索引边界检查
return new SubList(this, 0, fromIndex, toIndex);
}
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
//内部类SubList的构造函数:
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;
}
源码分析:
25.public Object[] toArray();
作用:将当前集合中的所有元素转化成顶级Object类型对象数组返回
源码如下:
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
源码分析:
ArrayList的数据结构本来就是一个数组,利用Object [] toArray()复制了一个对象
26.public Object[] toArray(Object []a);
作用:将集合中的元素依次存入参数数组中并返回。
源码如下:
public <T> T[] toArray(T[] a) {
if (a.length < size)//如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);//所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。
if (a.length > size)// 若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。
a[size] = null;
return a;
}
27.public String toString();
作用:一个arraylist转换为字符串
源码如下:
//inherited from AbstractCollection
public String toString() {
Iterator<E> it = iterator();//集合本身调用迭代器方法(this.iterator),得到集合迭代器
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
源码分析:
若集合没有元素->返回String类型"[]"
若集合有元素->先拼接'['->进入for死循环用append拼接e元素+','+' '->没有元素使用return跳出死循环并拼接']'
28.public void trimToSize();
作用:去除集合两端的null
源码如下:
public void trimToSize() {
modCount++;
if (size < elementData.length) {//如果时间大小小于缓冲区容量的长度,则进行数组复制。
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
源码分析:底层数组的容量调整为当前列表保存的实际元素的大小的功能
六.参考资料
Java 集合中常见 checkForComodification()方法的作用? modCount和expectedModCount作用?
java面试(ArrayList去重;hashcode与equals)
Java 集合中关于Iterator 和ListIterator的详解
ArrayList之removeAll底层原理实现详解
ArrayList中remove、removeAll、retainAll方法源码解析
用大白话告诉你ArrayList的底层原理