目录
前言
正文
1.需要了解的一些字段属性
1.存储 ArrayList 元素的数组缓冲区。
2.集合的大小
3.默认集合容量大小
2.ArrayList对象创建
1.无参构造
2.有参构造1
3.有参构造2
3.添加元素add(E e)以及扩容机制
编辑
后言
前言
源码的剖析有助于理解设计模式,优化编程思想,先叠个甲,我也是学习者,文章不妥处欢迎纠正,一起进步,文章的产出是根据优秀文章,自己研究和理解还有AI的辅助,同时我会以一个学习者(当然我本来就是个学习者)的角度对一些代码抛出自己的疑惑和理解,文章内容持续更新~~~
正文
1.需要了解的一些字段属性
1.存储 ArrayList 元素的数组缓冲区。
transient Object[] elementData;
就是用来存储集合中的元素的数组,他的大小等于集合的容量
以下两个字段属性都是提前预设好的,用于在不同情况下的对elemenData的初始化,不过两个都是空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
2.集合的大小
这个属性表示当前集合元素的个数也就是elementData中元素的个数
private int size;
3.默认集合容量大小
这个属性是在扩容的时候用的,并不是说无参创建一个对象,初始容量就是10,下面会讲到的。
private static final int DEFAULT_CAPACITY = 10;
2.ArrayList对象创建
1.无参构造
会将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,此时elementData为空数组
2.有参构造1
有参构造我们可以传入集合的初始容量,如果这个初始容量小于0,就会抛出非法容量的异常,如果等于0,就会把EMPTY_ELEMENTDATA赋值给elementData,也就是空数组,如果大于0,就会将elementData数组初始化成大小为初始化容量的数组
3.有参构造2
先来聊一下这个方法的形参:
Collection<? extends E> c
Collection表示实现了Collection接口,表示c是一个集合,可见,这个构造方法是将一个集合转变成一个新的ArrayList对象,这个E是ArrayList的类型参数,见下下图,我们定义ArrayList对象的时候,要给其指定元素的类型,例如:ArrayList<String> s = new ArrayList<>();?extends E就表示集合c的类型参数需要是E或者是E的子类,这很好理解,我们总不能把一个元素类型是String的集合A转变成元素类型是数字的ArrayList吧,也就是这种写法:
然后我们看方法体:
先调用集合c的toArray方法拿到集合c的Object类型元素集合
Object[] a = c.toArray();
先将数组a的长度赋值给size,然后判断是否为0
(size = a.length) != 0
如果是,就把EMPTY_ELEMENTDATA赋值给elementData,也就是空数组
else {// replace with empty array.elementData = EMPTY_ELEMENTDATA;}
如果不是,会先判断集合c是不是ArrayList类型,如果是,就直接吧集合a赋值给elementDate,如果不是,就会将数组a中的元素拷贝到一个新的Object[]类型的数组,然后赋值给elementDate
if ((size = a.length) != 0) {if (c.getClass() == ArrayList.class) {elementData = a;} else {elementData = Arrays.copyOf(a, size, Object[].class);}}
题外话:
这里我比较疑惑的一点在于,为什么要判断集合的类型,我去问了一下AI,AI是这么回答的:
其中第一点好理解,第二点就不理解了,他说是为了避免外部集合的修改导致新的ArrayList中的数据跟着出错,为了独立,但经过我的实操,两个集合间的数组是没有引用的,如下图,我创建两个相同类型的集合,让他走有参构造直接赋值的语句,输出结果表明两个集合间的数组是没有引用的,那么如果是类型不一样的集合,数组直接赋值是不是也行?
3.添加元素add(E e)以及扩容机制
先来解释一下modCount,这个字段用于记录集合被修改的次数,准备来说是使集合大小发生变化的修改,我们知道,集合是可以用迭代器进行遍历的,迭代过程中如果我们添加,删除元素等使集合发生大小变化的操作时,会报错,而其判断的依据就是modCount。
为什么不直接使用集合的大小是否变化来判断?
这个很好理解,添加一个元素再删除一个,大小不变
为什么迭代器遍历过程中需要避免直接修改集合?
不过,有些集合框架提供了特定的方法来在迭代过程中安全地修改集合。例如,java.util.ListIterator
接口(它是Iterator
的子接口,用于遍历List
类型的集合)提供了add
和set
方法,可以在遍历List
集合(如ArrayList
、LinkedList
等)的过程中安全地添加和修改元素。这些方法会在内部正确地处理集合结构的变化,并且更新迭代器的状态,以确保遍历的一致性和正确性。
modCount++;
让我们跟进add方法,传入了要添加的元素,elementData数组和元素的个数size
add(e, elementData, size);
add:
size其实也就是存放下一个元素的下标,先判断size是否等于elementData的长度,不等于,就直接在索引处放入e
elementData[s] = e;
如果等于,则数组满了,需要扩容
if (s == elementData.length)elementData = grow();
无论哪种情况,结束后让size+1
size = s + 1;
让我们继续跟进扩容:
grow():
继续跟进: grow(size+1):
这里的形参minCapacity的大小就是size+1,由于此时满容量了才来的,也就是原来的容量+1
int minCapacity
oldCapacity就是原来的容量
int oldCapacity = elementData.length;
如果oldCapacity>0或者emementData不为空,就通过ArraysSupport.newLength方法计算出新的长度,然后通过Arrays.copyOf方法创建一个新的数组,然后把就数组中的数据复制到新数组中,并将这个新数组赋值给elementData
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity >> 1 /* preferred growth */);return elementData = Arrays.copyOf(elementData, newCapacity);
}
否则,也就是原来的容量是0,此时的minCapacity为1,所以就创建一个容量为DEFAULT_CAPACITY也就是10的数组,终于知道定义的DEFAULT_CAPACITY在哪用了,所以最开始无参构造创建对象时,容量是0,并不是最开始就用了这个DEFAULT_CAPACITY,而是在扩容的时候用
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
让我们跟进ArraysSupport.newLength:
这里的三个形参的大小:oldlength就是原来的集合容量,minGrowth就是minCapacity-prefGrowth,也就是1,preGrowth是oldCapacity>>1,右移一位也就是原来的容量除以2,
int oldLength, int minGrowth, int prefGrowth
首先计算出一个新length----prefLength
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
如果这个prefLength大于0并且小于最大容量上限,就返回这个prefLength作为新的容量
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {return prefLength;}
这个最大容量是int的最大值减8
我们来聊一聊为什么是这个数:
首先,这个数能大于int的最大值吗,不行,有趣的是我们是通过int类型来指定数组的大小的,所有如果超过int,首先出问题的是数据类型那,这是语言层面的限制,并不是说我的电脑内存很大,创建数组完全可以超过这个数,而我们只需要知道这一个就行了(在此之前我搜各种文章,说什么有对象头的存在要占用一部分内存,集合容量大小不能超过这个数),这个容量上限其实是可以超过的,看接下来的代码即可
public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
如果这个数超过上限,就调用hugeLength方法,传入原来的容量大小和最小增长1
else {// put code cold in a separate methodreturn hugeLength(oldLength, minGrowth);}
hugeLength:
计算出minlength
int minLength = oldLength + minGrowth;
如果这个值小于0,就报错,我们知道,能进入这个方法,说明此时的oldLength已经很大了,已经超过了设置的上限,也就是int的最大值减8,如果小于0,就表示minLength已经超过了int的上限,超出上限后会变成负数,所以报错
if (minLength < 0) { // overflowthrow new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");}
如果这个数小于最大上限,就返回最大上限(感觉这一步有点怪~~~)
else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {return SOFT_MAX_ARRAY_LENGTH;}
重点来了!!!否则,返回这个长度,首先,我们要知道,如果能走到判断的最后一步,说明这个数没有超过int上限并且比最大上限要大,那么我们就可以知道,这个最大上限是可以超越的,并不表明集合的最大值不能超过他!!!
return minLength;
跟进了这么多方法,之后就是方法的回溯,将新的长度一直往上传递,然后添加新的元素,返回true
到此,add方法完毕.
后言
持续更新,如果发现错误欢迎指导和纠正