来,话不多说进入正题!我们下面用最简单的代码创建ArrayList并添加11个元素,并 一 一 讲解底层源码;在说之前,给大家先普及一些小知识:

  》ArrayList底层是用数组来实现的

  》数组一旦创建后,大小就是固定的,如果超出了数组大小后,就会创建一个新的数组

  》接下来所谓数组的扩容实质上是重新创建一个大小更大的新数组

    @Test
public void testArrayList() { //创建一个泛型为String的ArrayList(这里泛型是什么不重要)
ArrayList<String> list = new ArrayList<String>(); //依次添加11个元素
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
list.add("8");
list.add("9");
list.add("10");
list.add("11"); }

上面的代码中,我们就只调用了add(),在看add()源码前,我必须给你们先介绍一些在ArrayList的常量和变量,因为在接下来的源码中会涉及到这些,怕你们到时一脸蒙

private static final int DEFAULT_CAPACITY = 10;

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData;

private int size;

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  》DEFAULT_CAPACITY:default_capcity,默认的容量大小,也就是当你第一次创建数组并往里面添加第一个元素时,数组的默认容量大小

  》DEFAULTCAPACITY_EMPTY_ELEMENTDATA:defaultcapacity_empty_elementdata是默认的空数组,他的作用是当elementData为{},即空数组时,把它赋值给elementData,要是理解不了,请你往下继续看!

  》elementData:表示的就是当前存储元素的数组

  》size:他表示当前还没有添加新元素前的数组中有效的元素个数,比如说数组长度为10,只保存了5个元素,那有效长度就是5

  》MAX_ARRAY_SIZE:最大数组长度,它用来标识当前数组可保存元素的最大长度,值为Integer_MAX_VALUE -8,即2147483647 - 8 ,这里的 8 代表8字节用来保存数组本身的内存大小。

现在我们进入到add()里面看他们具体如何实现的,如下代码:

 public boolean add(E e) {
    
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} 

  》ensureCapacityInternal(size + 1):这个方法意为“确保内部变量”,什么意思呢?他是用来判断当前数组的容量是否足够,不足就扩容;等下我们会进入这个方法来看他如何具体实现的,size表示当前还未添加新元素前的数组有效元素个数,而size+1表示传入当前数组的最小容量(有效长度)
  》elementData[size++] = e:这段语句意思是给数组做赋值操作,简单说就是给数组添加元素;比如说当前数组已经有3个元素了,那现在再添加一个元素a,则这一步为elementData[3]=a;

  》return true:代表添加成功;

现在我们就进入到ensureCapacityInternal(),如下代码:

    private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

这里面涉及两个方法ensureExplicitCapacity()和calculateCapacity():

  》calculateCapacity():计算容量,它用来计算当前的数组所需的最小容量minCapacity, 你可以理解为当前数组的有效长度;源码如下:

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //若传入的是个空数组,则返回的是最小容量 是 默认容量(10) 和 当前最小容量(0)之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}

PS:第一次添加元素时calculateCapacity返回的最小容量minCapacity是10,从第二次开始minCapacity为2,第三次为3,依次类推..在这里第一次返回10大家不要纠结它的意义,重点在第二次及之后表示的意思

  》ensureExplicitCapacity():判断是否需要扩容;查看它的源码:

    private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code当最小容量大于当前的数组大小时
if (minCapacity - elementData.length > 0)
      //计算扩容后的数组大小
grow(minCapacity);
}

我们第一次list.add(),最小容量minCapacity是10,elementData.length长度为0,所以条件成立,进入grow()(第二次minCapacity是2,elementData.length为10,条件不成立就不再扩容了;当第11次时,11>10,又可以扩容了)

    private void grow(int minCapacity) {
// 得到当前数组的大小,即老数组大小
int oldCapacity = elementData.length;

//将旧数组大小+旧数组/2,即旧数组的1.5倍是新数组的大小(先不要在意>>1的意思,你只要知道oldCapacity >> 1表示oldCapacity/2就行)
int newCapacity = oldCapacity + (oldCapacity >> 1);

//如果扩容后的是数组大小还是小于最小所需容量,直接让minCapacity赋值到新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;

//若新容量大小大于数组长度的最大预设值;由于扩容后是原数组的1.5倍,则非常有可能会溢出这个预设值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);

// minCapacity is usually close to size, so this is a win:
//上面都是为了确定最终的新容量的大小,这个方法是真正的扩容实现
elementData = Arrays.copyOf(elementData, newCapacity);
}

相信大家这上面大部分都能够理解,可能就一个地方不太清楚:当newCapacity > MAX_ARRAY_SIZE(新容量大于预设值较特殊的情况,一般数组长度不会扩容到这么大)时调用hugeCapacity有啥用?我们看下hugeCapacity()的源码:

    private static int hugeCapacity(int minCapacity) {
//若最小容量小于0的情况,抛出异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//若最小容量>最大预设值,返回Integer.Max_VALUE,否则是MAX_ARRAY_SIZE(Integer.Max_VALUE-8)
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

hugeCapacity()是用来限制新容量的大小的,是不能超出Integer.MAX_VALUE值的,最后说一点,数组的最大长度并不是MAX_ARRAY_SIZE,而是Integer.MAX_VALUE。

  》Arrays.copyOf(elementData, newCapacity),就不看源码了,简单说一下:它这个方法能返回一个扩容后的数组,将旧数组elementData的数据复制到长度为newCapacity的新数组中。

好的,就介绍到这里吧,觉得可以的点给赞哦!

小白也能看懂的ArrayList的扩容机制的更多相关文章

  1. 小白也能看懂的插件化DroidPlugin原理(二)-- 反射机制和Hook入门

    前言:在上一篇博文<小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理>中详细介绍了 DroidPlugin 原理中涉及到的动态代理模式,看完上篇博文后你就会发现原来动态代 ...

  2. 小白也能看懂的插件化DroidPlugin原理(三)-- 如何拦截startActivity方法

    前言:在前两篇文章中分别介绍了动态代理.反射机制和Hook机制,如果对这些还不太了解的童鞋建议先去参考一下前两篇文章.经过了前面两篇文章的铺垫,终于可以玩点真刀实弹的了,本篇将会通过 Hook 掉 s ...

  3. 小白也能看懂的Redis教学基础篇——朋友面试被Skiplist跳跃表拦住了

    各位看官大大们,双节快乐 !!! 这是本系列博客的第二篇,主要讲的是Redis基础数据结构中ZSet(有序集合)底层实现之一的Skiplist跳跃表. 不知道那些是Redis基础数据结构的看官们,可以 ...

  4. 【vscode高级玩家】Visual Studio Code❤️安装教程(最新版🎉教程小白也能看懂!)

    目录 如果您在浏览过程中发现文章内容有误,请点此链接查看该文章的完整纯净版 下载 Linux Mac OS 安装 运行安装程序 同意使用协议 选择附加任务 准备安装 开始安装 安装完成 如果您在浏览过 ...

  5. 小白也能看懂的Redis教学基础篇——做一个时间窗限流就是这么简单

    不知道ZSet(有序集合)的看官们,可以翻阅我的上一篇文章: 小白也能看懂的REDIS教学基础篇--朋友面试被SKIPLIST跳跃表拦住了 书接上回,话说我朋友小A童鞋,终于面世通过加入了一家公司.这 ...

  6. 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)

    一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...

  7. ArrayList的扩容机制

    一.ArrayList的扩容机制 1.扩容的计算方式是向右位移,即:newSize = this.size + (this.size>>1).向右位移,只有在当前值为偶数时,才是除以2:奇 ...

  8. 关于ArrayList的扩容机制

    关于ArrayList的扩容机制 ArrayList作为List接口常用的一个实现类,其底层数据接口由数组实现,可以保证O(1) 复杂度的随机查找, 在增删效率上不如LinkedList,但是在查询效 ...

  9. 浅谈 ArrayList 及其扩容机制

    浅谈ArrayList ArrayList类又称动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问.但因为ArrayList中插入或删 ...

随机推荐

  1. 23种设计模式 - 领域问题(Interpreter)

    其他设计模式 23种设计模式(C++) 每一种都有对应理解的相关代码示例 → Git原码 ⌨ 领域问题 Interpreter 动机(Motivation) 在软件构建过程中,如果某一特定领域的问题比 ...

  2. 【Gin-API系列】Gin中间件之异常处理(六)

    本文我们介绍生产环境上如何通过捕捉异常recovery来完善程序设计和提高用户体验. Golang异常处理 golang 的异常处理比较简单,通常都是在程序遇到异常崩溃panic之后通过defer调用 ...

  3. 初级知识六——C#事件通知系统实现(观察者模式运用)

    观察者模式,绝对是游戏中十分重要的一种模式,运用这种模式,可以让游戏模块间的通信变得简单,耦合度也会大大降低,下面讲解如何利用C#实现事件通知系统. 补充,首先说下这个系统的实现原理,不然一头扎进去就 ...

  4. 使用Javax.mail 发送邮件

    使用Javax.mail 发送邮件 详细说明都在代码中: 引入依赖  <!--sun定义的一套接收.发送电子邮件的API-->    <dependency>      < ...

  5. Azure Storage 系列(二) .NET Core Web 项目中操作 Blob 存储

    一,引言 上一篇文章,我们介绍到在实际项目中系统会产生大量的日志文件,用户上传的头像等等,同时也介绍到可以使用Azure Blob Storage 来存储项目中的一些日志文件,用户头像,用户视频等等. ...

  6. PAT甲级1151(由前序和中序确定LCA)

    The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U ...

  7. POJ-2104-K-th Number(区间第K大+主席树模板题)

    Description You are working for Macrohard company in data structures department. After failing your ...

  8. 非IT行业大企程序员讲述MIS系统开发案例

      雪莉叹了一口气,调整了一下被汗水濡湿的刘海,然后向后靠在办公椅中,伸手在电脑键盘上输入了一些内容, 最后拿起印刷着房地产广告的扇子,边扇风边等待着.   她的工位在办公室的东侧角落,侧靠着窗.此时 ...

  9. 持续部署入门:基于 Kubernetes 实现滚动发布

    前言 软件世界比以往任何时候都更快.为了保持竞争力,需要尽快推出新的软件版本,而不会中断活跃用户访问,影响用户体验.越来越多企业已将其应用迁移到 Kubernetes. 在 Kubernetes 中有 ...

  10. FIddlerd的下载教程和使用教程

    ------------恢复内容开始------------ .打开官网,官网下载地址是https://www.telerik.com/download/fiddler .打开以后选择你的相关信息如下 ...