JDK8中ArrayList扩容机制

news2024/11/14 13:42:07

前言

这是基于JDK8的源码分析,在JDK6之前以及JDK11之后细节均有变动!!


首先来看ArrayList的构造方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; // non-private to simplify nested class access

    private int size;

    //如果new ArrayList时指定了初始化容量大小
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //创建一个长度为0的空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    //默认创建一个空数组
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            // 官方bug,如果有子类继承了AttayList并且子类中也存在一个toArray方法时会出现getClass()不为Object的情况。文章最后会给案例进行解释
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
}

上述代码中该注释的地方已经进行了注释,以上代码可以总结如下信息:

  • 对于无参构造,ArrayList会创建一个默认的空数组
  • 对于提供了初始化大小的构造器,先判断初始化大小值是否合理,如果初始化大小指定为0的情况,将另一个空数组给elementData属性。
  • 对于参数为Collection的构造器,ArrayList会将传入的列表用迭代器按顺序返回。

对于无参或是指定初始化大小为0的情况,他们真正分配容量是在第一次执行add()方法。

现在以无参构造器创建的ArrayList为例(初始化大小为0与无参创建的ArrayList扩容逻辑略有不同),接下来去查看add方法的源码进一步分析扩容逻辑

public boolean add(E e) {
    // 填加元素之前,先调用ensureCapacityInternal方法,因为要判断新添加的元素能否被装进当前数组当中,因此要进行+1
    // 这里+1的说法是我的猜想,如有不正确或是更好的说法请指出
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 执行添加元素的代码
    elementData[size++] = e;
    return true;
}

这里调用了ensureCapacityInternal方法,我们进一步查看该方法代码

// 根据给定的最小容量和当前数组元素来计算所需容量。
private static int ensureCapacityInternal(Object[] elementData, int minCapacity) {
    // 如果当前数组元素为空数组(初始情况),返回默认容量和最小容量(minCapacity此时为1)中的较大值作为所需容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//此时minCapacity为10
    }
    // 调用ensureExplicitCapacity方法
    ensureExplicitCapacity(minCapacity);
}

// 确保内部容量达到指定的最小容量。
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 以最小容量减去当前数组容量,小于则扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

分析此时扩容逻辑:

  • 当第一次进入add方法时,进入ensureCapacityInternal方法中的if块,变量minCapacity被赋值为10。随后调用ensureExplicitCapacity方法,满足if条件,执行grow方法进行扩容
  • 第二次进入add方法时,进入ensureCapacityInternal方法中的if不会再满足,于是此时的minCapacity为2去调用ensureExplicitCapacity方法去调用,此时的elementData.length为10,不满足扩容条件,直接返回即可。
  • 当第十一次次执行add方法时满足ensureExplicitCapacity方法中的扩容条件,执行扩容。

接下来去查看grow方法源码,查看具体扩容逻辑

private void grow(int minCapacity) {
    // oldCapcatity为旧容量 newCapcatity为新容量
    int oldCapacity = elementData.length;
    // 每次扩容1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 当第一次扩容时,旧容量为10,新容量为0,因此交换新旧容量的值
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 当新容量大于数组最大容量时,则调用hugeCapacity方法
    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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 如果最小容量大于最大最大容量那么新容量就是Integer的最大值,否则返回数组最大容量(Integer.MAX_VALUE - 8)
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

具体的扩容逻辑注释的应该很明白了,这里就不细节分析了。

之前注释案例解释

针对第三个构造函数为什么存在toArray返回结果可能不为Object。我们来看以下案例

//继承ArrayList类,泛型随便指定,这里只是测试
public class Haha extends ArrayList<String> {
    //这里返回类型也是随便指定
    public String[] toArray(){
        return new String[]{"1","2"};
    }
}
public class Test {
    public static void main(String[] args){
        ArrayList<String> strings = new ArrayList<>();
        Object[] array = strings.toArray();
        System.out.println(array.getClass());
        strings = new Haha();
        array = strings.toArray();
        System.out.println(array.getClass());
    }
}

输出结果如下

因此在ArrayList源码中会存在将数组元素类型强转为Object部分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1546424.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言-如何判断当前环境是大端存储还是小端存储

编写一个代码&#xff0c;判断当前环境是大端存储还是小端存储。 代码一&#xff1a; #include<stdio.h> int hanshu(int x) {int *p;p&x;return *(char*)p; } int main() {int a1; //00000001或者01000000if(hanshu(a)1){printf("小端存储");}else …

Spring设计模式-实战篇之单例模式

实现案例&#xff0c;饿汉式 Double-Check机制 synchronized锁 /*** 以饿汉式为例* 使用Double-Check保证线程安全*/ public class Singleton {// 使用volatile保证多线程同一属性的可见性和指令重排序private static volatile Singleton instance;public static Singleton …

Ubuntu20.04修改屏幕分辨率

Ubuntu20.04修改屏幕分辨率 使用命令行语句修改屏幕分辨率,并解决"xrandr: Configure crtc 0 failed"报错。 方法一 打开终端,输入xrandr,找到你当前使用的分辨率,比如1920x1080输入cvt 1920 1080,获取该分辨率的有效扫描频率输入sudo xrandr --newmode "…

秋招刷题2

1.字符串分割 public static void main(String[] args) {Scanner scnew Scanner(System.in);while(sc.hasNext()){String strsc.nextLine();StringBuilder sbnew StringBuilder();sb.append(str);int sizestr.length();int addZero8-size%8;while((addZero>0&&(addZ…

黑马鸿蒙学习(3):滑动条

1&#xff09; 滑动条slidebar属性&#xff1a;

安踏与耐克的赛场,不止在中国

安踏与耐克的赛场&#xff0c;不止在中国 文 | 螳螂观察 作者 | 易不二 2024年以来安踏集团喜讯不断。 继2月初亚玛芬登陆纽交所&#xff0c;成为北美资本市场2023年9月以来规模最大的IPO之后&#xff0c;安踏在近日又提交了一份再创历史新高的年报。从具体的财报数据来看&…

算法笔记~—位运算

目录 常见位运算&#xff1a; 1、基础位运算 2、对于一个数n。确定、修改这个数n二进制x位。 3、提取&#xff08;确定&#xff09;一个数n最右侧的1&#xff08;bit&#xff09;与干掉最右侧的1&#xff08;bit&#xff09; 4、异或运算律 5、位运算的优先级&#xff1a…

Focal Modulation Networks聚焦调制网络

摘要 我们提出了 焦点调制网络 &#xff08;简称 FocalNets) &#xff0c;其中 自注意&#xff08; SA &#xff09;被 Focal Modulation 替换&#xff0c;这种机制 包括三个组件&#xff1a;&#xff08; 1 &#xff09;通过 depth-wise Conv 提取分级的上下文信息&#xff0…

latex报错Undefined control sequence.

这里写目录标题 1. 错误原因2. 进行改正3. 爱思唯尔期刊与施普林格期刊对于算法的格式不太一样&#xff0c;不能直接套用总结---在LaTeX中&#xff0c;使用algorithm环境排版算法时&#xff0c;有一些格式注意事项 1. 错误原因 我在算法中使用\Require 2. 进行改正 换成\REQ…

YOLOv9改进策略:注意力机制 | 动态稀疏注意力的双层路由方法BiLevelRoutingAttention | CVPR2023

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; CVPR2023 动态稀疏注意力的双层路由方法BiLevelRoutingAttention&#xff0c;强烈推荐&#xff0c;涨点很不错&#xff0c;同时被各个领域的魔改次数甚多&#xff0c;侧面验证了性能。 &#x1f4a1;&#x1…

vue项目在本地源码方式启动和打包之后在nginx中代理有什么不同

Vue项目在本地源码方式启动和打包之后在Nginx中代理的主要区别在于开发环境与生产环境的配置、性能优化、安全性和部署流程等方面。以下是一些具体的差异点&#xff1a; 开发环境与生产环境&#xff1a; 本地源码启动通常是在开发环境中&#xff0c;使用Vue CLI的vue-cli-servi…

C++基础之继承续(十六)

一.基类与派生类之间的转换 可以把派生类赋值给基类可以把基类引用绑定派生类对象可以把基类指针指向派生类对象 #include <iostream>using std::cin; using std::cout; using std::endl;//基类与派生类相互转化 class Base { private:int _x; public:Base(int x0):_x(…

【Java多线程(2)】Thread常见方法和线程状态

目录 一、Thread类及常见方法 1. join() 等待一个线程 2. currentThread() 获取当前线程引用 3. sleep() 休眠当前线程 二、线程的状态 1. 线程的所有状态 2. 状态转移 一、Thread类及常见方法 接上文&#xff1a;多线程&#xff08;1&#xff09;http://t.csdnimg.cn/…

Docker 【安装MongoDB】

文章目录 前言一、安装二、使用1. 通过权限认证的方式登入2. 基础操作 前言 MongoDB是一个非关系型数据库&#xff0c;它主要的应用场景有这些 相比mysql&#xff0c;MongoDB没有事务&#xff0c;索引之类的东西。最小单位是文档。 可能有人说&#xff0c;为什么这个场景我要…

【无标题】如何使用 MuLogin 设置代理

如何使用 MuLogin 设置代理 使用 MuLogin 浏览器设置我们的代理&#xff0c;轻松管理多个社交媒体或电子商务帐户。 什么是MuLogin&#xff1f; MuLogin 是一款虚拟反检测浏览器&#xff0c;使用户能够管理多个电子商务、社交媒体和广告帐户&#xff0c;而无需验证码或 IP 禁…

canvas画带透明度的直线和涂鸦

提示&#xff1a;canvas画线 文章目录 前言一、带透明度的直线和涂鸦总结 前言 一、带透明度的直线和涂鸦 test.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content…

Chrome浏览器修改网页内容

方法一&#xff1a;使用开发者工具 在Chrome浏览器中打开要修改的网页。按下F12键打开开发者工具。在开发者工具窗口中&#xff0c;找到“Elements”标签页。在“Elements”标签页中&#xff0c;找到要修改的网页元素。双击要修改的网页元素&#xff0c;即可进行编辑。 方法二…

轻松掌握:使用 API 接口自动缩短网址的秘诀

在互联网的世界里&#xff0c;网址缩短已经成为了一种时尚和必要。长而复杂的网址不仅难以记忆&#xff0c;还可能让人望而却步。但是&#xff0c;现在有了 API 接口&#xff0c;我们可以轻松地将网址自动缩短&#xff0c;让分享变得更加简单和高效&#xff01;本文将以具体例子…

Protocol Buffers设计要点

概述 一种开源跨平台的序列化结构化数据的协议。可用于存储数据或在网络上进行数据通信。它提供了用于描述数据结构的接口描述语言&#xff08;IDL&#xff09;&#xff0c;也提供了根据 IDL 产生代码的程序工具。Protocol Buffers的设计目标是简单和性能&#xff0c;所以与 XM…

vue脚手架创建项目:账号登录(利用element-ui快速开发)(取消eslint强制格式)(修改端口号)

新手看不懂&#xff0c;老手不用看系列 文章目录 一、准备工作1.1 取消强制格式检查1.2 导入依赖&#xff0c;注册依赖 二、去element-ui官网找样式写Login组件2.1 引用局部组件2.2 运行项目 三、看一下发现没问题&#xff0c;开始修改前端的代码四、修改端口号4.1 修改后端端口…