================
//基础容量
private static final int DEFAULT_CAPACITY = 10;
//动态数组
transient Object[] elementData;
//空数组,供参数为集合或整数的构造器使用(详细请看下面的分析)
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组,供空参构造器使用(详细请看下面构造器的分析)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//动态数组的长度(集合中元素的个数)
private int size;
//记录数组被修改的次数,用于防止fail-fast(具体看下面的分析)
protected transient int modCount = 0;
//动态数组的最大容纳量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
光看属性似乎会有很多疑问或不解,大概对这些属性有个印象,结合下面的方法来看吧。
ArrayList 的构造器
==============
在 ArrayList 中,提供了三个构造器:空参构造器、参数为集合的构造器,参数为整数的构造器。
1. 空参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 参数为集合的构造器
public ArrayList(Collection<? extends E> c) {
//将集合转化为数组
elementData = c.toArray();
//判断c.toArray()的长度是否为0
if ((size = elementData.length) != 0) {
// 检查c.toArray()是否为Object类型的数组,如果不是,则重新拷贝成一个Object类型的数组
if (elementData.getClass() != Object[].class)
//使用了Arrays这个工具类来进行浅复制,关于Arrays工具类的实现请移步相关文章
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 如果c.toArray()的长度为0,那么就直接使用预定义的空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
3. 参数为整数的构造器
public ArrayList(int initialCapacity) {//将合法的整型参数作为动态数组的初始化容量
//如果初始化容量大于0,则将参数作为数组的初始化容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//如果参数等于0,则使用预定义的空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {//处理不合法的参数(参数小于0),抛出异常
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
常用的方法
=====
一、添加方法
在 ArrayList
中,提供了以下 4 个用于增加元素的方法:
1. boolean add(E e)
—— 该方法用于向集合的末尾添加一个元素,返回一个布尔值,表示操作是否成功。
public boolean add(E e) {
//检查容量是否满足,具体分析看下面
ensureCapacityInternal(size + 1);
//将元素添加到数组的末尾
elementData[size++] = e;
//所有错误都在相应的方法里面处理了,所以此处返回true
return true;
}
private void ensureCapacityInternal(int minCapacity) {//参数为最小容量
//判断动态数组是否为预定义的空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果为空数组,则取出 默认容纳量(10)和最小容量两者之中的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {//参数为最小容量(就是上面方法那两者的最大值)
//修改计数器自增
modCount++;
//如果最小容量比动态数组的长度大,则进行扩容
if (minCapacity - elementData.length > 0)
//真正扩容的方法
grow(minCapacity);
}
private void grow(int minCapacity) {
//动态数组没添加元素之前元素的个数
int oldCapacity = elementData.length;
//新容量是没添加元素之前元素个数的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量小于最小需要的容纳量,则将最小需要的容纳量作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大于预定义的动态数组最大容纳量(详细看属性那里),则将最大容纳量作为新容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//通过Arrays工具实现扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
//处理不合法参数
if (minCapacity < 0)
throw new OutOfMemoryError();
//如果需要的最小容纳量大于属性MAX_ARRAY_SIZE,则返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE
//注:MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 (详细看属性的定义)
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
2. void add(int,E)
—— 添加元素到指定的位置。
public void add(int index, E element) {
//处理不合法的参数index。其实就是检测数组的下标是否越界(详细的分析看后面)
rangeCheckForAdd(index);
//检测数组容量是否满足
ensureCapacityInternal(size + 1);
//这是一个native方法,死活不让我看源码!
//大致可以看出意思就是将index位置后面的所有元素往后移一位,空出index这个位置
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//在数组的index这个位置存储元素的值,从而就实现了插入元素的效果
elementData[index] = element;
//size是集合中元素的个数,自增
size++;
}
private void rangeCheckForAdd(int index) {
//如果索引大于数组的长度或者小于0,则为不合法,抛出异常。
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3. boolean addAll(Collection<? extend E>)
—— 用于合并两个集合,意思就是将两个集合的元素放在源集合中,返回布尔值,表示操作是否成功
public boolean addAll(Collection<? extends E> c) {
//老套路,先将集合转换为数组
Object[] a = c.toArray();
//转换后数组的长度
int numNew = a.length;
//还是熟悉的配方,这是检测容量的那套方法,前面已经分析过了。
ensureCapacityInternal(size + numNew);
//该死的native方法
//大致的意思就是将数组的所有元素拷贝到集合的末尾
System.arraycopy(a, 0, elementData, size, numNew);
//更新长度
size += numNew;
return numNew != 0;
}
4. boolean addAll(int,Collection<? extend E>)
—— 将一个集合中所有元素插入到源集合中的 index
位置,结果返回布尔值,表示操作是否成功。
public boolean addAll(int index, Collection<? extends E> c) {
//检测参数是否合法。下标是否越界,在前面已经分析过了。
rangeCheckForAdd(index);
//一样的套路:转化为数组
Object[] a = c.toArray();
int numNew = a.length;
//考虑容量
ensureCapacityInternal(size + numNew); // Increments modCount
//本地代码无法查看,大致意思就是将index位置后面所有元素往后移,空出a.length个位置,然后将数组的所有元素插入到其中。
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
二、修改与获取元素的方法
在 ArrayList
中,修改与获取元素的方法分别只有一个,就是 get
和 set
。
E set(int index, E element)
—— 修改 index
位置的元素,结果返回被修改的元素。
public E set(int index, E element) {
//这个方法与前面判断越界的方法有所不同,具体看下面的分析
rangeCheck(index);
//获取旧的值
E oldValue = elementData(index);
//设置新的值
elementData[index] = element;
return oldValue;
}
private void rangeCheck(int index) {
//显然,不同之处就是少了一个条件:index < 0,这也就是意味着可以传递一个负数作为参数
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E get(int index)
—— 获取 index
位置的元素。
public E get(int index) {
//检查下标是否越界
rangeCheck(index);
//返回数组index位置的元素
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
三、删除元素的方法
在 ArrayList
中,提供了四个用于删除元素的方法。
1. E remove(int index)
—— 移除集合中某个位置的元素,结果返回这个元素。
public E remove(int index) {
//检测下标是否越界
rangeCheck(index);
//修改计数器
modCount++;
//获取指定位置的元素
E oldValue = elementData(index);
//本地方法。。。
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
return oldValue;
}
2. boolean remove(Object o)
—— 删除某个元素,如果集合中存在多个,则删除首次出现的,结果返回布尔值,表示是否成功。
public boolean remove(Object o) {
//分开两种情况考虑,一种为元素为null,另一种为非null,但是做法都是一致,遍历数组,拿到元素对应的索引,然后删除。
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
刷面试题
刷题的重要性,不用多说。对于应届生或工作年限不长的人来说,刷面试题一方面能够尽可能地快速自己对某个技术点的理解,另一方面在面试时,有一定几率被问到相同或相似题,另外或多或少也能够为自己面试增加一些自信心,可见适当的刷题是很有必要的。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
-
前端字节跳动真题解析
-
【269页】前端大厂面试题宝典
将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频**
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
[外链图片转存中…(img-jRoHVQSw-1710605115759)]
刷面试题
刷题的重要性,不用多说。对于应届生或工作年限不长的人来说,刷面试题一方面能够尽可能地快速自己对某个技术点的理解,另一方面在面试时,有一定几率被问到相同或相似题,另外或多或少也能够为自己面试增加一些自信心,可见适当的刷题是很有必要的。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
-
前端字节跳动真题解析
-
【269页】前端大厂面试题宝典
最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。