新手入门了解ArrayList扩容机制

我们下面用最简单的代码创建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的新数组中。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java基于ArrayList实现群主发红包功能

    群主发普通红包,某群有多名成员,群主给成员发普通红包,普通红包的规则: 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取: 成员领取红包后,保存到成员余额中. 请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作. 根据题目可以稍作分析,群主和普通群成员都隶属于用户类,那么久可以建立一个用户类,用户类的属性可以有用户名,金额或者钱包,如下: package day05_after03; /** * 定义成员类 * * @author liuwenlong *

  • 浅谈numpy中np.array()与np.asarray的区别以及.tolist

    array和asarray都可以将结构数据转化为ndarray,但是主要区别就是当数据源是ndarray时,array仍然会copy出一个副本,占用新的内存,但asarray不会. 1.输入为列表时 a=[[1,2,3],[4,5,6],[7,8,9]] b=np.array(a) c=np.asarray(a) a[2]=1 print(a) print(b) print(c) 从中我们可以看出np.array与np.asarray功能是一样的,都是将输入转为矩阵格式.当输入是列表的时候,更改

  • @ConfigurationProperties绑定配置信息至Array、List、Map、Bean的实现

    相关说明: 在SpringBoot中,我们可以通过以下几种方式获取并绑定配置文件中的信息: @Value注解. 使用Environment. @ConfigurationProperties注解. 通过实现ApplicationListener接口,注册监听器,进行硬编码获取,可参考:https://www.jb51.net/article/187407.htm 硬编码加载文件获取. -- 注:一般情况下,第一种.第二种就够用了;但是如果想直接从配置文件中获取到数组.list.map.对象的话,

  • python 解决mysql where in 对列表(list,,array)问题

    例如有这么一个查询语句: select * from server where ip in (....) 同时一个存放ip 的列表 :['1.1.1.1','2.2.2.2','2.2.2.2'] 我们希望在查询语句的in中放入这个Ip列表,这里我们首先会想到的是用join来对这个列表处理成一个字符串,如下: >>> a=['1.1.1.1','2.2.2.2','2.2.2.2'] >>> ','.join(a) '1.1.1.1,2.2.2.2,2.2.2.2' 可

  • Java ArrayList如何实现生成不重复随机数

    在此之前我使用Java的数组实现了产生N-M之间的不重复的随机数,下面是使用数列ArrayList实现同样的功能,代码如下: /** * 随机生成 N--M,N个不重复随机数 使用ArrayList * * @param startRange 起始数字 * @param endRange 终止数字 * @param count 个数 */ public static ArrayList<Integer> getRandom(int startRange, int endRange, int c

  • Java二维数组与动态数组ArrayList类详解

    Java二维数组 Java 语言中提供的数组是用来存储固定大小的同类型元素. 1.二维数组初始化和声明 数组变量的声明,和创建数组可以用一条语句完成,如下所示: int a[][] = new int[2][3]; int[][] arr = {{1,2,3},{4,5,6},{7,8,9}}; 2.二维数组遍历 //遍历二维数组 public class Traverse_a_two_dimensional_array { public static void main(String[] ar

  • 聊一聊jdk1.8中的ArrayList 底层数组是如何扩容的

    一.结论先行 ArrayList在JDK1.8与JDK1.7底层区别 JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍 JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍 二.JDK1.8 ArrayList源码分析 1.ArrayList 属性 /** * 默认容量的

  • 新手入门了解ArrayList扩容机制

    我们下面用最简单的代码创建ArrayList并添加11个元素,并 一 一 讲解底层源码:在说之前,给大家先普及一些小知识: >ArrayList底层是用数组来实现的 >数组一旦创建后,大小就是固定的,如果超出了数组大小后,就会创建一个新的数组 >接下来所谓数组的扩容实质上是重新创建一个大小更大的新数组 @Test public void testArrayList() { //创建一个泛型为String的ArrayList(这里泛型是什么不重要) ArrayList<String&

  • 详解ArrayList的扩容机制

    目录 一.ArrayList 了解过吗?它是啥?有啥用? 二.ArrayList 如何指定底层数组大小的 三.数组的大小一旦被规定就无法改变 四.ArrayList 具体是怎么添加数据的 五.ArrayList 又是如何删除数据的呢 六.ArrayList 是线程安全的吗?不安全的表现 七.为什么线程不安全还要用它呢 一.ArrayList 了解过吗?它是啥?有啥用? 众所周知,Java 集合框架拥有两大接口 Collection 和 Map,其中,Collection 麾下三生子 List.S

  • 对Java ArrayList的自动扩容机制示例讲解

    注意: 不同的JDK版本的扩容机制可能有差异 实验环境:JDK1.8 扩容机制: 当向ArrayList中添加元素的时候,ArrayList如果要满足新元素的存储超过ArrayList存储新元素前的存储能力,ArrayList会增强自身的存储能力,已达到存储新元素的要求 ArrayList:本质通过内部维护的数组对象进行数据存储 ①:分析ArrayList的add(E)方法 public boolean add(E e) { ensureCapacityInternal(size + 1); /

  • Java基础之ArrayList的扩容机制

    我们知道Java中的ArrayList对象底层是基于数组实现的,而数组是有长度限制的,那基于数组实现的ArrayList是否有长度限制呢?我们通过ArrayList的构造方法来剖析 ArrayList提供了3种构造方法以便我们来获取: ArrayList(int initialCapacity) 第一种需要赋值长度进行new ArrayList() 第二种无参构造,不需要赋值数组初始长度 ArrayList(Collection<? extends E> c) 第三种入参一个继承了Collec

  • 关于ArrayList的动态扩容机制解读

    目录 1. 前言 2. ArrayList 的动态扩容机制 2.1. ArrayList 的主要属性 2.2. ArrayList 的构造器 2.3. ArrayList 的动态扩容 3. 小结 3.1. 初始容量 3.2. 动态扩容大小 3.3. 动态扩容大小测试 1. 前言 对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却. 所以利用这篇文章做做笔记,加深理解记忆 2. ArrayList 的动态扩容机制 要了解其动态扩容机制就必须先

  • add方法理解ArrayList的扩容机制

    目录 ArrayList的构造方法(前置知识) ArrayList的add方法(理解扩容机制) add 添加元素 得到最小扩容量 判断是否需要扩容 扩容方法 ArrayList的构造方法(前置知识) 可快速过 一些基本成员变量: // 默认初始大小 private static final int DEFAULT_CAPACITY = 10; // 空数组 用于空实例 private static final Object[] EMPTY_ELEMENTDATA = new Object[0];

  • 干货 | Linux新手入门好书推荐

    前言 经常有读者问小编可否推荐一些 Linux 入门书籍,正好最近在知乎也看到类似的问题,如几个零碎的命令难以在 Linux 环境中存活,所以如果要真正形成自己的知识体系,还是要靠阅读专业书籍来积累. 众所周知Linux 对后端开发是必备技能,对 Python 开发者来说重要性不言而喻,将来你写的每一行代码,都有可能在 Linux 环境中运行.前端开发是否有必要学习 Linux 呢?这个就好比学驾照,学到了,总有一天会给你带来便利,暂时没时间学的可以先收藏着. linux之路,路漫漫其修远兮,吾

  • Android Studio 新手入门教程(一)基本设置图解

    ##写在前面: 作为一个刚半只脚踏入android开发的新手,在使用eclipse开发了两个自我感觉不甚成熟的商城类app之后,遇到了一些问题,总结为如下: 1.代码复用性 .findviewById,onclick事件等,一遍遍重复这类无聊的代码简直浪费生命,这个问题推荐通过依赖注入框架ButterKnife解决,直接一键生成布局中的所有控件,包括onclick点击事件,但是诸如行布局item里的控件,以及布局中include复用的布局要如何使用框架解决,这个有待后续再看. 另一个代码重复率很

  • android studio 新手入门教程(二)项目的导入教程图解

    上篇文章介绍了AS的一些常用设置方法,当工具调教妥当后,自然就要开始项目的开发啦.从零开始新建一个项目,这个简单,不必多说,这篇博客会分享我从旧平台eclipse导入项目到AS的过程,以及遇到的一些问题并如何解决.开篇先粗略的提一些需要注意的地方. ##结构目录 和eclipse不同,在android 视图下的项目目录分为java,res和manifests. manifests目录存放清单文件,不必多说. java目录会默认生成三个文件夹,其中test为在本机执行单元测试代码的目录, andr

  • android studio 新手入门教程(三)Github( ignore忽略规则)的使用教程图解

    Android Studio 里集成了上传代码到 github 的功能,所以使用上还是很简单的,设置里添加账号并测试,之后就可以很方便地上传代码到 github 了 如果你的项目是使用Android Studio新建的,那么关于 github 基本就没什么问题了.Android Studio新建项目是自带 .ignore 文件的,也就是说默认是使用了忽略规则上传.默认忽略的是 *.iml .gradle /local.properties /.idea/workspace.xml /.idea/

随机推荐