【数据结构】深度解析ArrayList()的无参构造

前言:本文会解析到关于ArrayList(顺序表)中的三个方法,并深度解析对于ArrayList()的无参构造是怎样进行的。

一、ArrayList简介

在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:

然后对于ArrayList中:

【说明】

  1. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
  2. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
  3. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
  4. Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
  5. 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()的无参构造。

附带:整个分析的流程图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恒等于C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值