Java集合框架(二):JDK1.7的ArrayList源码解析 您所在的位置:网站首页 arraylist源码17 Java集合框架(二):JDK1.7的ArrayList源码解析

Java集合框架(二):JDK1.7的ArrayList源码解析

2023-08-31 09:46| 来源: 网络整理| 查看: 265

ArrayListJDK1.7变量和常量方法构造新增元素add(E e)方法add(int index,E element)方法 删除元素clear清空集合remove index方法 remove object方法修改元素克隆方法

ArrayList JDK1.7

我们首先来看看JDK1.7的ArrayList 在这里插入图片描述 可以看到ArrayList继承了AbstractList,同时也实现了List接口,这里的List接口是冗余的了,因为AbstractList也是实现了List接口,所以只需要去继承了AbstractList就好了,作者也承认了这方面问题

所以ArrayList拥有AbstractList所有方法,也就是有一些Collection接口的默认实现和List接口的默认实现

变量和常量

接下来,我们看看ArrayList里面有哪些变量和常量 在这里插入图片描述 里面总共有2个变量,4个常量

DEFAULT_CAPACITY:底层数组默认容量,默认为10(静态常量)elementData:底层Object数组,被transient修饰,代表不可被序列化(变量)EMPTY_ELEMENTDATA:空的底层数组(静态常量)MAX_ARRAY_SIZE:底层数组允许最大的容量,默认为Integer最大值减8(静态常量)serialVersionUID:版本ID,也是序列化ID,用于反序列化(静态常量)size:底层数组里面元素个数(变量) 方法

方法的话,我们从构造、增加、删除、查看、修改去看ArrayList

构造

在这里插入图片描述 如果确定了容量,也就是使用第一个构造方法,会有以下几个步骤

super调用AbstractList的构造方法,来实例化父类判断指定容量是否合法(不能小于0),如果不合法,抛出异常将底层Object数组进行初始化,容量为指定容量

如果没有确定容量,也就是使用第二个构造方法,会有以下几个步骤

super调用AbstractList的构造方法,来实例化父类将底层数组设为EMPTY_ELEMENTDATA,也就是一个空数组好像在更早期的时候JDK1.7版本是直接调用this(10)来构造的,视频看到的,我也不知道什么版本相比于直接this(10)去实例化ArrayList,直接将底层数组设置为一个空数组更加节省内存,因为不知道你实例了之后到底去不去用,不用的话,那这个空间就浪费了,所以当要add的时候才进行给底层数组申请内存空间 新增元素

在这里插入图片描述 可以看到有新增有两个方法,两个都是重写AbstractList的add方法(AbstractList这两个方法都是直接抛出异常,所以必须要进行重写)

add(E e)方法

这个方法分为三步

检查底层数组容量是否足够(是否可以再增加元素),minCapacity就是代表新增了元素之后,底层数组里面此时总共有多少个元素(注意这里是还没有新增的,新增的操作是下一步)底层Object数组增加元素,位置在size(size记录了底层数组的元素个数,那么新的元素在数组的索引位置就是size),然后size++返回true,表示新增成功

后面两部都很容易理解,关键在于ensureCapacityInernal这里

我们来看看他是怎么实现的 在这里插入图片描述 步骤如下

判断底层Object数组是否是EMPTY_ELEMENTDATA空数组 如果是就对minCapactity进行赋值,值为DEFAULT_CAPACITY和minCapacity的最大值DEFAULT_CAPACITY就是前面提到的静态常量,默认为10所以可以看出,如果是无参构造方法来实例化ArrayList,第一次调用add方法,minCapacity一定为10 第一步判断是针对第一次添加的,那如果不是第二次添加呢?,接下来就需要调用ensureExplicitCapacity(Explicit:清除明白),也就是再一步去弄清楚底层数组容量

接下来进入到ensureExplicitCapacity 在这里插入图片描述 步骤如下

modCount:这个变量是在AbstratList中的,根据文档来看,是用来记录底层数组修改过的次数 在这里插入图片描述

接下来判断新增需要的数组容量是否大于此时底层Object数组的长度

如新增需要的数组容量不大于底层Object数组的长度,就不需要进行操作,直接返回,执行下一步的新增元素如果新增需要的数组容量大于底层Object数组的长度,那么就需要进行扩容,调用grow方法

扩容(超重点)

扩容方法是调用grow方法 在这里插入图片描述 步骤如下

使用oldCapacity记录此时底层Object数组的长度

newCapacity记录扩容后新数组的长度,规则为旧的长度的1.5倍(右移一位,所以这里后面的0.5倍是一个向下取整)

判断扩容后新的长度是否满足需要,即是否大于minCapacity(增加元素需要的最小空间)

如果不满足需要,即newCapacity小于minCapacity,那就让newCapacity变为minCapacity,直接让扩容后的容量为增加元素需要的最小空间

接下来判断扩容后的数组长度是不是大于MAX_ARRAY_SIZE(这个值为Integer的最大值减去8)

如果大于MAX_ARRAY_SIZE,就会调用hugeCapacity(但这个方法不是再AbstractCollection已经有实现了吗?直接调用不行吗?)

在这里插入图片描述 从这里判断就可以看出,hugeCapacity顶多也就能再加8,直接给Integet的最大值

最后调用Arrays.copyof方法来讲旧数组原有的数组转移到新数组中,然后讲新数组赋值给elementData变量,相当于扩容产生了另外一个新数组 至此,扩容就结束了

add(int index,E element)方法

接下来我们看另一个add方法,指定位置去增加元素

由于ArrayList的底层是一个Object数组,所以指定位置去增加元素,因为往数组中增加元素是需要进行复杂操作的,增加的位置后面的那些元素都要往后移一位 在这里插入图片描述 步骤分别如下

首先调用rangeCheckForAdd来看要插入的位置(index)是否合理 在这里插入图片描述 判断的规则是,不可以小于0,和大于size(此时的数组元素个数,对应的就是底层数组的下一个新增元素位置),也就是可以往后面添加,但不可以隔开最后一个,然后往再后面一个进行添加,这是为了维持List的有序性

接下来到判断底层数组容量是否足够(即上面提到的扩容)

然后调用System.arrayCopy方法,将底层数组从index位置后面的元素都往后移动一格,即腾出index的位置

接下来让底层Object数组的index索引对应的值设为指定值

最后让size自增

该方法为void型,不会有返回值

删除元素 clear清空集合

在这里插入图片描述 步骤如下

同理让modCount自增,代表底层数组又发生变化了这里遍历底层Object数组,将所有的元素都设为空,让gc回收机制去回收最后将size设置为0,代表底层数组里面元素被清空,不包含任何元素

注意,这里gc回收的是数组里面的引用对象,而不是数组的内存

所以,此时底层Object数组的长度依然是之前的,这样做的好处就是,下次再继续add的时候,就不再需要进行扩容

remove index方法

在这里插入图片描述 指定索引去删除元素

步骤如下

modCount自增1使用elementData获取旧的元素使用arrayCopy的方法将后面的元素移上前来,空出了最后一个底层数组位置将底层数组最后一个位置设为null,让gc收集引用的内存将旧的元素返回 remove object方法

在这里插入图片描述 该方法是指定值去删除,同理也是分成null与非null的去进行

通过遍历底层数组,然后使用equals方法进行比较底层数组的各个元素,找到匹配的索引,然后调用fastRemove方法

接下来看看fastRemove方法是怎样的 在这里插入图片描述 在这里插入图片描述 每一次删除指定index位置,都是让底层数组指定索引后面的元素向前移一位,然后将底层数组的最后一位设为null

不太懂为什么要叫fastRemove,哪里fast了??

修改元素

修改元素对应的方法就是set,而该方法是有返回值的,返回被替代或被修改的元素 在这里插入图片描述 步骤如下

第一步同样是检查index是否合理(与add第二种方法一样)

调用ekementData根据Index索引获取旧的元素 在这里插入图片描述

然后修改底层Object数组指定索引对应的值即可

克隆方法

这里再拓展以下ArrayList的clone方法 在这里插入图片描述 通过注释可以得知ArrayList的克隆是一个浅拷贝,也就是克隆出来的数组不一样,但是里面存储的引用对象是一致的,修改其中一方另一方都会改变

这里catch用了前面复习java异常提到过的,使用catch来改变异常的类型

回到clone方法这里,这个clone方法是重写Object的

调用父类的clone方法,这里对应的是调用Object的clone,拷贝一个ArrayList然后将拷贝出来的新ArrayList的底层数组复制当前ArrayList的elementData然后将拷贝出来的新ArrayList的modCount设置为0,代表为新的,底层Object数组没有改动过

不过有点疑问,为什么拷贝出来的ArrayList可以直接点出私有属性出来???

看网络上的回答,私有的意思是不让外界访问本类(实现对外隐藏保护),那么该类在该类自己的clone方法里面拷贝的另一个对象的类型也是属于本类,那么就不应该限制访问,因为这就相当于限制自己访问自己的私有东西,是不合理的。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有