ArrayList源码解析于相关知识点(超级详细)
首先ArrayList是List接口的数组实现类。查询、修改效率高、新增、删除效率低,线程不安全。
ArrayList的继承关系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
由以上代码可以看出ArrayList继承了AbstractList,实现了List、RandomAccess、Cloneable、Serializable接口。
ArrayList类的继承结构图如下:
可以看出ArrayList继承了AbstractList,AbstractList已经实现了List接口,为什么还有让ArrayList继续实现List接口?
Serializable标记接口
实现Serializable接口是为了实现序列化和反序列化。序列化:将对象的数据写到本地文件;反序列化:将本地相关文件中的对象数据读取出来。
Cloneable标记接口
是一个标记性接口
实现Cloneable接口是为了使用clone()方法。实现了Cloneable接口后,调用clone()方法可以对于该类的实例进行字段的复制。
使用clone()方法的前提条件:
- 实现Cloneable接口;
- 重写clone()方法。
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();//super.clone()调用的是Object中的clone()方法。
//调用Arrays类的copyOf方法。实现克隆
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);
}
}
/*copyof方法*/
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);
//Array.newInstance()创建一个具有指定组件类型和长度的新数组,返回的是Object类型
//数组的拷贝
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
//arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));是将原来的original数组 拷贝到copy新数组中
return copy;//返回新数组
}
由上面的代码可知:克隆返回的是新的地址。
克隆又分为浅克隆和深克隆。
-
浅克隆
/*创建Student类实现Cloneable接口,重写clone()方法*/ public class Student implements Cloneable { private String name ; private int age ; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //重写时访问权限不能比父级低,返回值类型不能比父级高(jdk7、jdk8之后) @Override public Student clone() throws CloneNotSupportedException { return (Student) super.clone(); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
/*测试类: 先创建一个Student对象stu1 在通过克隆创建stu2,可以看见两个对象的地址不同,内容相同, 然后修改stu1的名字,stu2是不会改变的。 */ public class Test { public static void main(String[] args) throws CloneNotSupportedException { Student stu1 = new Student("张三", 20); Student stu2 = stu1.clone(); System.out.println(stu1==stu2);//地址比较 System.out.println(stu1); System.out.println(stu2); stu1.setName("王五"); System.out.println("=============="); System.out.println(stu1); System.out.println(stu2); } } /*false Student{name='张三', age=20} Student{name='张三', age=20} ============== Student{name='王五', age=20} Student{name='张三', age=20}*/
当类中包含引用类型时:
//在上面的基础上添加年级grade 成员变量 Grade未实现Cloneable接口 public class Student implements Cloneable { private String name ; private int age ; private Grade grade; public Student(String name, int age , Grade grade) { this.name = name; this.age = age; this.grade = grade; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Grade getGrade() { return grade; } public void setGrade(Grade grade) { this.grade = grade; } @Override public Student clone() throws CloneNotSupportedException { return (Student) super.clone(); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", grade=" + grade + '}'; } }
public class Test { public static void main(String[] args) throws CloneNotSupportedException { Grade grade = new Grade("大一"); Student stu1 = new Student("张三", 20,grade); Student stu2 = stu1.clone(); System.out.println(stu1==stu2); System.out.println(stu1); System.out.println(stu2); //修改stu1的年级 grade.setName("大二"); stu1.setGrade(grade); System.out.println("=============="); System.out.println(stu1); System.out.println(stu2); } } /*false Student{name='张三', age=20, grade=Grade{name='大一'}} Student{name='张三', age=20, grade=Grade{name='大一'}} ============== Student{name='张三', age=20, grade=Grade{name='大二'}} Student{name='张三', age=20, grade=Grade{name='大二'}}*/
发现stu2的年级也改变了。
浅克隆存在的问题:基本数据类型可以完全克隆,引用类型不行。
原因是:引用类型的变量在克隆时,仅仅只是拷贝了一份引用,当该引用变量内容改变时,所有使用此引用的变量地方的 值都会改变。
-
深克隆:
解决浅克隆的问题。让Grade类实现Cloneable接口,重写clone()方法。然后改变原来Student类当中的clone()方法。
/*Student类中修改clone方法*/ @Override public Student clone() throws CloneNotSupportedException { // return (Student) super.clone(); // 1.克隆student对象 Student stu = (Student) super.clone(); // 2.克隆Grade对象 Grade grade =(Grade) this.grade.clone(); // 3.将grade 赋值给stu stu.setGrade(grade); //返回 return stu; }
RandomAccess标记接口
此标记接口由List实现使用,表示支持快速随机访问。实现此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能。
随机访问:
//随机访问就是随机给一个索引就可以访问
for (int i = 0; i < list.size(); i++) {
list.get(i);//这就是随机访问,随机访问第i个值;
}
顺序访问:
//顺序访问是从头到尾的
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
iterator.next();
}
AbstractList类
是一个抽象类,在api中这样介绍:此类提供的骨干实现的List接口以最小化来实现该接口有一个"随机访问"数据存储备份所需的工作对于顺序存取的数据,AbstractSequentialList应;优先使用此类。要实现一个不可修改的列表,程序员只需要扩展这个类并提供get(int) 和size()方法的实现。要实现一个可以修改的列表,程序员必须覆盖set(int ,E) 方法,否则会抛出异常信息,如果列表大小可变,则程序员需要覆盖add(int ,E)和remove(int) 方法。
属性
/*实现Serializable接口后给的唯一标识*/
private static final long serialVersionUID = 8683452581122892189L;
/*设置默认容量=10*/
private static final int DEFAULT_CAPACITY = 10;
/*声明一个空数组,用于空实例的共享空数组*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/*
其注释的翻译为:
“用于默认大小的共享空数组实例。将其和EMPTY_ELEMENTDATA区别开来是为了知道第一次加入数据时扩容的大小”.
简单来说就是用来区分是从哪个构造函数来初始化ArrayList的。*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*集合真正存放元素的数组的容器,transient阻止被序列化的*/
transient Object[] elementData; // non-private to simplify nested class access
/*集合的大小*/
private int size;
声明:数组的elementData的大小称为容量capacity;集合的大小称为size,表示实际存储元素的个数。
构造函数
ArrayList共有三个构造函数
- 无参构造函数
- 指定初始容量的构造函数
- 传入集合的构造函数
无参构造函数
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
无参构造中是将DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组赋值给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) {
if (initialCapacity > 0) {
//elementData直接赋值为一个initialCapacity大小的数组对象
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//EMPTY_ELEMENTDATA赋值给elementData,,
//值得注意的的是,此时与无参构造相同都不分配容量,但是赋值的变量不同,目的就是为了区分是哪个构造器来初始化的;
this.elementData = EMPTY_ELEMENTDATA;
} else {
//initialCapacity小于0时报出容量不合法的消息;
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
传入集合
创建一个包好指定集合的元素的列表,按照他们由集合的迭代器返回的顺序。
/**
* 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) {
Object[] a = c.toArray();//将传入的集合改为数组;
if ((size = a.length) != 0) {//判断集合的大小,并把大小传给size属性
if (c.getClass() == ArrayList.class) {//判断c集合的类型是不是ArrayList.class
elementData = a;//如果是直接传
} else {
//如果不是这先将其转为Object[].class
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
添加方法
有四种:
方法 | 方法说明 |
---|---|
public boolean add(E e) | 添加指定元素 |
public void add(int index, E element) | 在列表的指定位置添加指定元素 |
public boolean addAll(Collection<? extends E> c) | 将指定集合的所有元素按顺序追加到列表的尾部 |
public boolean addAll(int index, Collection<? extends E> c) | 从指定的位置开始将指定集合的所有元素添加到列表中 |
public boolean add(E e) 方法
/**
* Appends the specified element to the end of this list.
*在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(size + 1); // Increments modCount!!
elementData[size++] = e;//正式添加值e到size位置,size自增
return true;//返回true
}
private void ensureCapacityInternal(int minCapacity) {
//先将实际存放数组的容器和添加一个元素后的长度传入calculateCapacity方法中
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//判断elementData是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA默认的空数组,如果是选择无参构造,在构造列表的时候是不赋予实际容量的,在此时判断是否是第一添加元素;
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//是,就在添加一个元素后的长度minCapacity和默认长度10中返回一个大的值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//不满足,就返回minCapacity添加一个元素后的长度
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//实际修改集合的次数加加
// overflow-conscious code
if (minCapacity - elementData.length > 0)//判断是否需要扩容 ,传过来的容量要求如果比实际存储容器elementData的容量大就需要扩容
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
* //minCapacity渴望的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//原本容量
//新容量 = 原本容量 + (原本容量/2):这里是经典的总结扩容到原来的容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
//如果是第一次添加元素,可能原本的容量就是0,0的1.5倍还是零,
//此处判断新容量是否小于渴望容量minCapacity,
//如果小于就将新容量的值改为渴望容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//这里是做一个保障用的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//将原来容器中的元素复制到新容量容器中
}
public void add(int index, E 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).
* 在指定的位置插入指定的元素,并且将该位置之后的元素后移
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
* index 指定的位置
* element 指定的元素
*/
public void add(int index, E element) {
rangeCheckForAdd(index);//判断索引是否超界
//与上面的添加方法一样
ensureCapacityInternal(size + 1); // Increments modCount!!
/*这里是此方法的一个重点:做法是先将index及index以后的元素后移,空出index位置方便添加元素*/
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;//在index位置添加元素
size++;//size自增
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)//判断索引是否在0-size中
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public boolean addAll(Collection<? extends E> c)方法:
/*
将指定集合的所有元素按顺序追加到列表的尾部
*/
//这个方法看起来和构造方法中,传入集合构造类似
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();//将传入集合转为Object数组
int numNew = a.length;//传入集合的长度
// 是否进行扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 附加到集合中:将数组a从0索引开始复制到elementData容器中从size位置开始,复制的长度为numNew
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c)方法:
/*
从指定的位置开始将指定集合的所有元素添加到列表中
*/
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)//如果移动的元素个数大于0,就将elementData中index之后的元素后移numNew位,长度为numMoved
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//如果需要移动的元素个数不大于0,肯定==0,这就如同上一个方法一样直接在末尾添加
//从index位置添加元素
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
修改方法
public E set(int index, E element)方法:
public E set(int index, E element) {
rangeCheck(index);//判断索引是否合法
E oldValue = elementData(index);//获得指定索引位置的元素
elementData[index] = element;//将该位置的元素赋值为指定元素element
return oldValue;//返回被替换的元素
}
private void rangeCheck(int index) {
//此方法不检查索引是否为负:它总是在数组访问之前立即使用,如果索引为负,则抛出ArrayIndexOutOfBoundsException。
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
获取方法
方法 | 描述 |
---|---|
public E get(int index) | 根据索引获取集合中的元素 |
public int indexOf(Object o) | 根据元素获取元素在该集合中第一次出现的索引位置 |
public int lastIndexOf(Object o) | 根据元素获取元素在该集合中最后一次出现的索引位置 |
public E get(int index)方法:
public E get(int index) {
rangeCheck(index);//判断索引是否合法
return elementData(index);//返回指定索引位置的元素;
}
public int indexOf(Object o)方法:
/*根据元素获取元素在该集合中第一次出现的索引位置*/
public int indexOf(Object o) {
if (o == null) {//判断需要查询的元素是否为null
//如果是null遍历集合容器寻找是否有等于null的元素,从这里可以看出ArrayList集合是可以存储null值的
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//如果不为null,同样遍历寻找
for (int i = 0; i < size; i++)
//判断相等用的是equals方法比内容
if (o.equals(elementData[i]))
return i;
}
//如果集合中不包含需要查询的元素则返回-1
return -1;
}
public int lastIndexOf(Object o)方法:
/*获取指定元素最后一次出现的索引*/
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;
}
转换方法
public String toString()方法:
在ArrayList集合对象调用toString()方法的时候调用的是AbstractCollection类中的toString方法。由上面的第一张图可以知道,ArrayList继承了AbstractList,而AbstractList类继承了AbstractCollection类所以是可以调用AbstractCollection类里的toString()方法的.
public String toString() {
//获取迭代器
Iterator<E> it = iterator();
if (! it.hasNext())//判断集合是否为空
return "[]";//为空返回"[]"
//创建StringBuilder类型对象sb以便返回
StringBuilder sb = new StringBuilder();
sb.append('[');//先添加'['
for (;;) {//设置一个无线循环
E e = it.next();//取出迭代器中光标指向的元素,并将光标移动到下一个元素
//三元判断,如果取出来的e==this就拼接一个(this Collection),否则拼接e
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())//判断是否还有元素,没有时就为sb添加']',将sb转为string类型返回
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
迭代器Iterator
迭代器是一种取出集合元素的方法。来自于单列集合根接口,所以所有的单列集合都可使用iterator()ArrayList也对其进行了重写。
Java中的集合各式各样,很多应用场景下需要对集合里面的元素进行遍历,有的集合有下标(ArrayList、Vector、LinkedList),有的集合没有下标。有下标的集合可以轻易地通过下标进行遍历,没有下标的集合遍历起来就非常麻烦,因此Java引入了迭代器模式,迭代器可以使得我们能够使用一种通用的方式遍历各种集合元素。
/*在ArrayList中的iterator()方法*/
public Iterator<E> iterator() {
return new Itr();
//Itr是ArrayList的一个私有的内部类
//private class Itr implements Iterator<E>
}
/**/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return 光标,默认值为0;类似于C语言的指针
int lastRet = -1; // index of last element returned; -1 if no such 返回最后一个元素的索引
//将集合实际修改次数赋值给预期修改次数
int expectedModCount = modCount;
//初始化
Itr() {}
//判断集合是否有元素
public boolean hasNext() {
//判断光标是否不等于集合的size,Itr是内部类可以访问外部的成员变量
return cursor != size;//不相等就返回true
}
//next方法
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//校验,为类的并发修改做准备的 Itr内部方法
int i = cursor;//将光标赋值给i
if (i >= size)//判断光标是否大于size
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;//吧集合存储数据的容器,赋值给该方法的局部变量
if (i >= elementData.length)
//如果发生会抛出并发修改异常,主要针对多线程
throw new ConcurrentModificationException();
//光标向下移动
cursor = i + 1;
//从数组中取出元素并返回
return (E) elementData[lastRet = i];
}
//迭代器中的删除方法
public void remove() {
if (lastRet < 0)//判断最后一个元素的索引是否小于0
throw new IllegalStateException();
checkForComodification();//校验,为类的并发修改做准备的 Itr内部方法
try {
ArrayList.this.remove(lastRet);//调用ArrayList类中的remove方法,并将最后一个元素的索引传入
//将光标移动到lastRet位置
cursor = lastRet;
lastRet = -1;//lastRet恢复为-1
//将实际修改次数重新赋值给预期修改次数,此处表名为什么迭代器对象的方法在遍历时删除元素时可以的,因为它重新赋值了预期修改次数
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
//校验预期修改此时是否与实际修改次数相同
final void checkForComodification() {
if (modCount != expectedModCount)
/*如,在获得迭代器,正在遍历时对集合进行增、删操作,
会引起实际修改次数改变,而致使与预期修改次数不同,
但是不是只要在遍历期间进行了这些操作就一定会抛出并发异常
如果删除的元素时集合中倒数第二个元素,就不会抛出异常,因为在删除时进行了size自减操作,在hasNext()方法中光标==size时返回false就不会调用next()方法;
*/
throw new ConcurrentModificationException();
}
}
删除方法
方法 | 描述 |
---|---|
public E remove(int index) | 根据索引删除元素 |
public boolean remove(Object o) | 删除指定元素(第一次出现的) |
public void clear() | 清除集合中所有元素 |
public boolean removeAll(Collection<?> c) | 删除集合中包含指定集合的所有元素 |
public E remove(int index)方法:
/*根据索引删除元素*/
public E remove(int index) {
rangeCheck(index);//判断索引是否合法
modCount++;//实际修改集合的次数加加
E oldValue = elementData(index);//获取指定索引位置的元素;
int numMoved = size - index - 1;//确定需要向前移动的元素有多少个
if (numMoved > 0)
//如果0个以上就将待删除位置以后的所有元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;//将size减1,并将size位置元素置空 // clear to let GC do its work
return oldValue;//返回被删除的元素
}
public boolean remove(Object o)方法:
/*删除指定元素(第一次出现的)*/
public boolean remove(Object o) {
if (o == null) {//判断指定元素是否为null
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);//将指定元素的索引传给快速删除方法
return true;//返回true
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);//将指定元素的索引传给快速删除方法
return true;//返回true
}
}
return false;//集合中没有指定元素就返回false
}
private void fastRemove(int index) {
modCount++;//实际操作数加一
int numMoved = size - index - 1;
if (numMoved > 0)
//将待删除位置以后的所有元素向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;//将size减1,并将size位置元素置空 // clear to let GC do its work
}
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;//size归零
}
public boolean removeAll(Collection<?> c)方法:
/*删除集合中包含指定集合的所有元素*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);//判断指定集合是否为空
return batchRemove(c, false);//传入指定集合,和false
}
//Objects类的静态方法
public static <T> T requireNonNull(T obj) {
if (obj == null)//如果为空,抛出空指针异常
throw new NullPointerException();
return obj;
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;//将容器地址赋给elementData
int r = 0, w = 0;//r:elementData遍历的索引 w:elementData待插入位置的索引
boolean modified = false;//返回标识
try {
for (; r < size; r++)//遍历原容器
//判断指定集合中是否包含elementData[r]
if (c.contains(elementData[r]) == complement)
//不包含时:将elementData[r]插入到elementData待插入位置
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {//如果r!=size就将r之后的元素移至w之后
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;//将w移至元素后一位
}
if (w != size) {//w != size时将w及其之后的元素置空
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;//更改实际操作次数
size = w;//更新size
modified = true;//更改返回标识
}
}
return modified;
}
包含方法
public boolean contains(Object o)方法:
public boolean contains(Object o) {
return indexOf(o) >= 0;//调用获取指定元素的索引的方法,返回是否大于等于0就可以
}
判断是否为空方法
public boolean isEmpty() {
return size == 0;//返回size是否等于0;
}
常见问题
-
ArrayList频繁扩容导致性能下降如何解决?
简单解决方法就是使用含参构造,创建时传如集合的一个指定的容量。 -
ArrayList是不是一个线程安全的?
不是线程安全的 -
如何将一个ArrayList复制到另一个ArrayList集合中:
- 使用clone()方法;
- 使用ArrayList构造方法;
- 使用addAll方法。
-
如何在多线程环境下使用迭代器在读取集合数据的同时添加数据?
有一个类它实现了读写分类:CopyOnWriteArrayList集合类。
它是ArrayList的一个变体,且是线程安全的。 -
ArrayList和LinkedList的区别?
ArrayList- 基于动态数组的数据结构
- 对于随机访问的get和set,ArrayList要优于LinkedList
- 对于随机操作add和remove,ArrayList不一定比LinkedList慢。
LinkedList
- 基于链表的数据结构
- 对于顺序操作,LinkedList不一定比ArrayList慢
- 对于随机操作,LinkedList效率明显低一点