前言:本文会解析到关于ArrayList(顺序表)中的三个方法,并深度解析对于ArrayList()的无参构造是怎样进行的。
深度解析ArrayList的无参构造
一、ArrayList简介
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:
然后对于ArrayList中:
【说明】
- ArrayList实现了
RandomAccess
接口,表明ArrayList支持随机访问 - ArrayList实现了
Cloneable
接口,表明ArrayList是可以clone的 - ArrayList实现了
Serializable
接口,表明ArrayList是支持序列化的 - 和
Vector
不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector
或者CopyOnWriteArrayList
- ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
这些只需要理解即可,后面会再详细分析这一些接口的。
二、ArrayList构造
然后我们来看一下对于ArrayList的构造:
方法 | 解释 |
---|---|
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他 Collection 构建 ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
这里就是我们对于ArrayList使用的方法,我们可以看见变化其实就在于参数中,我们分开了以代码的角度看一下:
1.ArrayList()
无参构造
ArrayList()无参构造就是在创建的时候不需要参数去创建,至于他的初始化的内存是多少,是怎么样初始化的,我们在后面会讲到,这里我们先演示是怎么创建的:
public static void main(String[] args) {
// ArrayList创建,推荐写法
List<Integer> list1 = new ArrayList<>();
list1.add(1);//将1存入顺序表,下同
list1.add(2);
list1.add(3);
// 构造一个空的列表
}
2.ArrayList(int initialCapacity)
上面的是没有参数的,这里就是有参数的了,这里在方法后面增加的参数就是关于这个顺序表的初始容量,也就是大小:
public static void main(String[] args) {
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
}
3.ArrayList(Collection<? extends E> c)
这个模板看起来是很奇怪的,但是实际上使用也只是作为一个参数,并没有上面太大的变化,上面说明到这是利用其他 Collection 构建 ArrayList,所以创建的参数其实就是用其他List的大小作为参数:
public static void main(String[] args) {
// 构造一个具有10个容量的列表
List<Integer> list2 = new ArrayList<>(10);
list2.add(1);
list2.add(2);
list2.add(3);
ArrayList<Integer> list3 = new ArrayList<>(list2);
list3.add(1);
list3.add(2);
}
三、深度解析ArrayList的无参构造
预备知识:ArrayList的
add
方法
我们先看一下关于ArrayList的add方法,我们可以直接在上面的代码上写上一个list1.add(1)
,然后我们可以按住Ctrl + 鼠标左击add,这样我们就可以看见add的源码:
这两个add方法其实简单理解就是看看有没有容量,然后有就放,没有的话就要进行另外的操作了。
接下来我们详解一下关于ArrayList的无参构造
首先创建下面这样的代码:
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
//ArrayList的初始化容量是多少?
for (int i = 0; i < 100; i++) {
list.add(i);
}
}
接下来开始我们的深度解析ArrayList()的无参构造:
(由于分析有点长,接下来我们用一张白纸按顺序分析下去:)
要了解这个方法是怎么弄的,最好的办法就是去看看他的源码,同样是按住Ctrl + 鼠标左击ArrayList,我们来看看这里的ArrayList<>()
:
1.点击查看到其实是一个数组,而且给这个数组赋值了:
2.然后我们再点一下这个数组elementData
看看他的参数,其中是一个没有初始化的数组,以及一个表示当前顺序表中有效数据的个数:
3.虽然没有初始化,但是我们上面有一个赋值呀,我们看看赋值是多少,点击赋值给elementData的一长串:
运行到这一步的时候,我们就会发现,其实在我们构造ArrayList的时候,它的底层虽然是一个数组,但是这个数组没有大小,或者说大小为0!那为什么当我们add的时候,存放元素的时候为什么可以成功,而不是越界?我们接着就要重新认识一下add这个方法了:
1.首先还是来到我们add的方法中,我们看见一个名字很长的函数,那么我们看见后面带的参数是size+1
,很明显这其实上一个确定容量的函数,起码得确认是否扩容:
2.然后我们再进入这个函数看看,进入之后其实又看见了两个函数,我们这里把他拆分一下,便于大家理解,这个capacity
就是新建的一个替补,我们继续看下去:
3.从第二步我们走到又一个函数里面,我们现在又是一个新的函数,里面有两个参数一个是elementData
,一个是我们传进去的1,我们继续往里面走,看看这个函数是干什么的,在这里面,我们首先判断是否满足,实际上是满足的,因为我们一开始就是这个值赋给elementData
,然后返回里面两者最大值,我们ctrl点过去知道default_capacity
的值是10,所以返回10:
4.然后我们再回到上一个函数中,将10放入第二个函数中,再进入里面,看见的这个函数中,modcount++
其实是记录修改顺序表的次数,这个与线程安全有关,然后后面那个才是最主要的,它是在判断参数和数组长度大小比较,如果参数比数组长度大,就会执行这个扩容函数,而现在后面放满就扩容,是因为我们数组现在长度是0:
5.我们进入扩容函数一探究竟,首先是得到老容量的值,然后是一个右移运算,相当于1.5倍扩容,然后再判断扩容后的值和参数谁大,参数大就将扩容值变成参数值,然后再判断扩容值有没有超出范围,也就是顺序表的大小也是有范围的,如果超出,就执行hugecapacity
这个函数,我们继续看:
6.进入之后我们看见,我们传入的值其实并没有运行第一个,后面其实是返回一个较大值,也就是说明超出范围后,至多只能返回的最大值:
7.而实际上我们这里并没有超出范围,所以我们应该是到最后一步,最后一步才是真正的扩容了,将扩容值拷贝给数组,也就是分配内存,这里是分配了10,完成扩容:
总结:
当我们进行ArrayList()
的无参构造时,初始大小其实是0,然后当我们第一次add(存放数据元素)的时候,会分配大小为10给数组,而当10个放满的时候,就是1.5倍扩容。
如果调用的是给定容量的构造方法,也就是后面带参数的那种,那么顺序表的大小,就是你给定的容量,放满了也是进行1.5倍扩容。
这就是我们的深度解析ArrayList()的无参构造。
附带:整个分析的流程图