栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【源码】走一遍源码弄清ArrayList容器的扩容机制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【源码】走一遍源码弄清ArrayList容器的扩容机制

【源码】走一遍源码弄清ArrayList容器的扩容机制

首先我们来看看ArraysList容器在整个Java集合框架中所处的位置

由此可见ArrayList是Java集合框架中,两大派系中Collection接口的子接口List的实现类

我们从源码入手可以看到ArrayList底层数据结构实际上是一个**Object类型的数组**,由于Object是所有类的父类,所以ArrayList集合可以存放任意类型的元素,并且存放的元素是有序的、可重复的、可随机访问的。

接下来我们就以add()方法为突破口,来解读一下ArrayList的源码,以及它的扩容机制

我们要想向ArrayList结合添加元素,首先得创建出一个ArrayList对象出来,ArrayList的构造方法有3种:

  • 无参构造器

    使用无参构造器创建一个对象时,默认创建的是一个空集合,它的初始容量是10。什么鬼?空集合怎么还容量为10,这不自相矛盾吗?我这里说的空集合指的是,这个集合刚创建出来,在还没有调用add()方法向集合中添加第一个元素之前,这个集合是空的。而所说的初始容量为10,指的是当你调用add()方法向集合中添加第一个元素时,这个集合的容量大小会被扩容到10,这个10就是指的初始容量。可以我们为向容器中添加了一个元素,为什么要扩容到10呢?这不浪费内存空间吗?实际上这是考虑的扩容的问题,你创建这个容器肯定不止存放一个元素,我们之所以冗余了一些空间,就是为了防止不断频繁扩容导致性能低下的问题。

  • 带初始容量参数的构造函数

    我们在创建一个容器时,在构造方法中可以传入初始容量大小,如果传入的初始容量大小为0的话,就会创建一个空的容器。

    注意!这里的DEFAULTCAPACITY_EMPTY_ELEMENTDATA和无参构造方法中的EMPTY_ELEMENTDATA虽然都是空集合,但是这两个空集合是有区别的,DEFAULTCAPACITY_EMPTY_ELEMENTDATA在添加第一个元素时,明确指定了扩容到默认的初始容量大小。而EMPTY_ELEMENTDATA由于指定的初始容量为0,所以在扩容的时候,会调用grow()方法走扩容的流程。

  • 传入一个集合参数的构造器

    在创建ArrayList容器时,可以给构造器传入一个集合参数,利用Arrays.copy()方法复制这些集合中元素,原来创建一个新的集合

现在我们利用构造器创建出来了一个ArrayList容器,接下来就调用add()方法开始向容器中添加元素。

在向容器中添加元素时,首先会调用ensureCapacityInternal()方法确保容器容量足够,得到最小扩容量

如果是使用默认的无参构造器创建的容器,并且是添加第一个元素,minCapacity 为 10,也就是当前添加元素的操作,需要容器的容量大小为minCapacity

确定了最小容器容量之后,就调用ensureExplicitCapacity()方法根据得到的最小容量来判断到底需不需要进行扩容

只有当需要的最小容量大于当前容器的最大容量时,最大的容量也就是数组的长度,就会调用grow()方法进行扩容

从grow()方法可以看出ArrayList容器的扩容机制是变为原来容量的1.5倍

为了防止在添加第一个元素时,由于空集合的容量为0,扩大1.5倍之后计算出的新的容量还是0,这样情况,源码中还通过比较扩容后的新的容量与需要的最小容量,来判断是否是第一次添加元素,如果是就直接将扩容后的新的容量改成需要的最小容量。这样就避免了第一次扩容扩不起来的问题。

如果说扩容后的容量超过了ArrayList的最大容量,也就是扩容1.5倍后的容量大于了Integer.MAX_VALUE - 8。此时就需要判断需要的最小容量是否超过了ArrayList的最大容量。

如果需要的最小容量超过了ArrayList最大容量,此时我们就不能按照1.5倍扩容了,直接返回Integer.Max_VALUE,如果说没有超过ArrayList最大容量,就返回最大容量

扩容完之后,就调用Arrays.copyOf()方法,开始将原来容器中的所有元素复制到扩容后的容量中

Arrays.copyOf()方法底层调用的实际上是System.arraycopy()本地方法,将原数组中的数据进行拷贝,并返回新的数组

至此,ArrayList容器的扩容流程就完成了!

我们注意到ArrayList的增删改查相关的方法都没有使用Synchronized同步关键字,或者说没有使用其他同步机制,因此ArrayList容器是线程不安全的!

与ArrayList容器对标的是Vector这个容器

Vector的底层实现和ArrayList的底层实现类似,也是使用Object类型的数组存放元素。而且默认初始容量也为10

与ArrayList不同的地方在于Vector的扩容的方法使用了Synchronized同步关键字,因此是**线程安全的!**而且Vector扩容机制是扩大2倍!而ArrayList是扩大1.5倍!

一般开发中都是使用ArrayList容器,Vector容器基本上不再使用,虽然Vector使用Synchronized保证了线程安全,但是效率十分低下,如果考虑到线程安全问题,可以使用JUC并发集合!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/284363.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号