ArrayList集合源码解读(二)
前言
这篇文章已经把 ArrayList
更完了。各位还想看什么源码可以私信我~~
上节课带大家阅读了 ArrayList
中的核心扩容代码,那么今天带大家阅读下List
集合中我们常用的几个方法的底层实现逻辑!
常用方法解读
笔者为大家讲解list
集合类中常用的几种操作,咱们一起看看他们的底层源码是如何实现的。
大家要区分set()
和add()
的区别,不要弄混了。
-
int size()
:返回此列表中的元素数。 -
boolean isEmpty()
:如果此列表为空,则返回 true 。 -
boolean contains(Object o)
:如果此列表包含指定的元素,则返回 true 。 -
int lastIndexOf(Object o)
:返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回 -1。 -
E get(int index)
:返回此列表中指定位置的元素。 -
E set(int index, E element)
:用指定的元素替换此列表中指定位置的元素。 -
boolean add(E e)
:将指定的元素追加到此列表的末尾。 -
void add(int index, E element)
:在此列表中的指定位置插入指定的元素。 -
E remove(int index)
:删除该列表中指定位置的元素。 将任何后续元素向左移动一位(从其索引中减去一个元素)。
源码:
// 返回此列表中的元素个数
public int size() {
return size;
}
// ------------------------------------
// 如果集合为空,返回 true
public boolean isEmpty() {
return size == 0;
}
// ------------------------------------
/**
* 如果此列表包含指定元素,则返回true。更正式地说,当且仅当此列表包含至少一个元素value,使得Objects.
* equals(o,value)时,返回true
*
* @param value 要测试其是否在此列表中的元素
* @return 如果此列表包含指定元素,返回true
*/
public boolean contains(Object value) {
// contains 方法底层调用的是 indexOf(Object o) 中的 indexOfRange(Object o, int s, int e)
return indexOf(value) >= 0;
}
// 底层方法实现
public int indexOf(Object o) {
// 向下调用 indexOfRange() 方法,传入三个参数:需要查找的元素 查找起始索引 查找结束索引
return indexOfRange(o, 0, size);
}
// 真正实现逻辑的方法
int indexOfRange(Object o, int start, int end) {
// 1.获取当前集合中的元素 (ArrayList 底层是使用 Object[] 存储元素数据的)
Object[] es = elementData;
// 2.如果待查找的元素 o 为 null,则从 start 起始索引开始遍历到 end 结束索引,找到第一个等于 null 的元素后返回其索引值
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
// 3.如果待查找的元素 o 不为 null,则则从 start 起始索引开始遍历到 end 结束索引,找到第一个等于 o 的元素后返回其索引值
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
// 没找到的情况,返回 -1
return -1;
}
// ------------------------------------
// 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回 -1。
public int lastIndexOf(Object o) {
// 底层调用lastIndexOfRange() 来实现,传入三个参数:待查找的元素值 o,起始索引,结束索引
return lastIndexOfRange(o, 0, size);
}
// 底层真正实现逻辑的方法
// 其实和上方的 indexOfRange() 方法类似,唯一变化的就是:
// indexOfRange() 是从开始索引遍历到结束索引;而 lastIndexOfRange() 是从结束索引倒序遍历。
int lastIndexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
// 倒序遍历数组元素
for (int i = end - 1; i >= start; i--) {
if (es[i] == null) {
return i;
}
}
} else {
// 倒序遍历数组元素
for (int i = end - 1; i >= start; i--) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
// ------------------------------------
/**
* 返回此列表中指定位置的元素。
*
* @param index 要返回的元素的索引
* @return 此列表中指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(索引<0||索引>=size())
*/
public E get(int index) {
// 检查索引是否合法 如果 index < 0 || index >= size 那么抛出异常 (size 是集合中的元素个数)
Objects.checkIndex(index, size);
// 底层调用 elementData(int index) 方法实现
return elementData(index);
}
// 底层真正实现逻辑的方法
@SuppressWarnings("unchecked") // @SuppressWarnings() 注解的作用是让编译器忽略检查,不要飘黄线
E elementData(int index) {
// 直接返回当前索引对应的元素
return (E) elementData[index];
}
// ------------------------------------
/**
* 将此列表中指定位置的元素替换为指定元素。
*
* @param index 要替换的元素的索引
* @param element 要存储在指定位置的元素
* @return 先前位于指定位置的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(索引<0||索引>=size())
*/
public E set(int index, E element) {
// 检查索引是否合法 如果 index < 0 || index >= size 那么抛出异常 (size 是集合中的元素个数)
Objects.checkIndex(index, size);
// 获取指定索引的元素,上面有调用,此次不重复讲解
// 因为要返回原先处于该位置的元素,所以需要先用个变量保存
E oldValue = elementData(index);
// 替换掉旧元素
elementData[index] = element;
return oldValue;
}
// ------------------------------------
/**
* 在此列表中的指定位置插入指定的元素。
* 先调用 rangeCheckForAdd 对index进行界限检查;然后调用 grow 方法保证 capacity 足够大;
* 再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
*/
public void add(int index, E element) {
// 检查索引是否合法
rangeCheckForAdd(index);
// 版本号 +1
// 此列表在结构上被修改的次数。结构修改是指改变列表大小的修改,或者以其他方式扰乱列表,使得正在进行的迭代可能会产生不正确的结果。
modCount++;
final int s;
Object[] elementData;
// 这句话的意思是 首先将 size 的值赋值给局部变量 s,再将数组底层存储数据的 Object[] elementData 赋值给局部变量 elementData。最好判断他们的长度是否一样
if ((s = size) == (elementData = this.elementData).length) {
// 扩容的核心方法 grow() 上面有讲,此处不再过多陈述
elementData = grow();
}
// System.arraycopy() 数组拷贝方法很重要!!
// 第一个参数是要复制的数组 a1
// 第二个参数是从a1的哪个索引开始赋值
// 第三个参数是要复制到哪个数组 a2
// 第四个参数是从 a2 这个数组的哪个索引开始覆盖
// 第五个参数是指复制多少个元素
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
// 判断索引是否符合规则
// 和上方的 Objects.checkIndex() 方法的区别在于:
// rangeCheckForAdd() 方法判断的区间是 index < 0 || index > size
// checkIndex() 方法判断的区间是 index < 0 || index >= size
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}