Java中的ArrayList容量及扩容方式

作者:zx2015216856 时间:2023-10-17 17:24:40 

查看JDK1.8 ArrayList的源代码

1、默认初始容量为10


  /**
    * Default initial capacity.
    */
   private static final int DEFAULT_CAPACITY = 10;

2、最大容量为 Integer.MAX_VALUE - 8


   /**
    * The maximum size of array to allocate.
    * Some VMs reserve some header words in an array.
    * Attempts to allocate larger arrays may result in
    * OutOfMemoryError: Requested array size exceeds VM limit
    */
   private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

原因:之前参考别人的,有待求证:

数组对象有一个额外的元数据,用于表示数组的大小;

数组长度size为int类型,共32位,有一位符号位,所以最大长度为Integer.MAX_VALUE=2^31= 2,147,483,648;

8bytes用来存储size;

3、扩容方式:

(1)首先传递进来一个希望的最小容量minCapacity;

(2)新容量newCapacity = oldCapacity + (oldCapacity >> 1),即新容量等于原容量的1.5倍;

(3)如果minCapacity > newCapacity ,newCapacity = minCapacity ;

(4)如果 newCapacity > 最大容量 MAX_ARRAY_SIZE ,newCapacity = hugeCapacity(minCapacity);

(5)以新容量拷贝原数据


  /**
    * Increases the capacity to ensure that it can hold at least the
    * number of elements specified by the minimum capacity argument.
    *
    * @param minCapacity the desired minimum capacity
    */
   private void grow(int minCapacity) {
       // overflow-conscious code
       int oldCapacity = elementData.length;
       int newCapacity = oldCapacity + (oldCapacity >> 1);
       if (newCapacity - minCapacity < 0)
           newCapacity = minCapacity;
       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();
       return (minCapacity > MAX_ARRAY_SIZE) ?
           Integer.MAX_VALUE :
           MAX_ARRAY_SIZE;
   }

Java ArrayList() 扩容原理

平常都是直接使用 ArrayList(),今天特地看一下 ArrayList() 的扩容原理。

先看下 ArrayList 的属性以及构造方法,这个比较重要

先看下属性


// ArrayList 默认容量大小
private static final int DEFAULT_CAPACITY = 10;
// 一个共享的空数组, 在空实例时使用
private static final Object[] EMPTY_ELEMENTDATA = {};
// 一个共享的空数组, 在使用默认 size 的空实例时使用
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
存储 ArrayList 元素的数组缓冲区
重点1: ArrayList 的容量是数组缓冲区的长度
重点2: 从这个元素也可以看的出来 ArrayList() 的底层就是一个 Object[]  
add 第一个元素时, 任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将被扩展为 DEFAULT_CAPACITY
*/
transient Object[] elementData;
// ArrayList 的大小, 我们平常使用的 list.size() 底层就是记录的这个 size
private int size;
// ArrayList 最大
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

再看构造器,带参构造器


/*
带参构造器, 参数为容量大小, 例如: 初始化一个容器为 20 类型为 Integer 的 ArrayList
ArrayList<Integer> list = new ArrayList<>(20);
*/
public ArrayList(int initialCapacity) {
/*
初始化容量 > 0, elementData 初始化为初始化容量大小的数组
初始化容量 = 0, elementData = EMPTY_ELEMENTDATA (空数组)
初始化容量 < 0, 直接抛出异常
*/
   if (initialCapacity > 0) {
       this.elementData = new Object[initialCapacity];
   } else if (initialCapacity == 0) {
       this.elementData = EMPTY_ELEMENTDATA;
   } else {
       throw new IllegalArgumentException("Illegal Capacity: "+
                                          initialCapacity);
   }
}

参数为 Collection 的构造器


/*
将一个参数为 Collection 的集合转换为 ArrayList
*/
public ArrayList(Collection<? extends E> c) {
   // Collection 转换为数组 Object[] 类型
   elementData = c.toArray();
   // 判断当前对象大小是否和 Collection 长度相等并且不等于 0, false 的话 elementData 等于空数组了
   if ((size = elementData.length) != 0) {
    // c.toArray() 可能不会正确地返回一个 Object[] 数组,所以使用 Arrays.copyOf()
       if (elementData.getClass() != Object[].class)
           elementData = Arrays.copyOf(elementData, size, Object[].class);
   } else {
       this.elementData = EMPTY_ELEMENTDATA;
   }
}

不带参构造器


/*
不带参构造器就像我们平时使用一样, 直接 new 一个 ArrayList 不需要传递任何参数
构造方法中直接将 elementData 初始化为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA (空数组)
*/
public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

看到这里就发现,带参的构造器我们可以直接传递参数,而默认的构造器怎么怎么样初始化容量大小的呢?

add() 方法可以直接得到答案。


public boolean add(E e) {
// 这一行是关键, 看下面
   ensureCapacityInternal(size + 1);
   // 将元素追加到集合的末尾 假如当前 size = 10 size++ 追加到第 11 位
   elementData[size++] = e;
   return true;
}

ensureCapacityInternal() 方法调用


private void ensureCapacityInternal(int minCapacity) {
/*
calculateCapacity() 方法, 刚初始化时会返回 10, 其他情况返回当前 size + 1
ensureExplicitCapacity() 方法, 调用扩容
*/
   ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/*
使用无参构造器创建创建 ArrayList 的集合, 此时一定是相等的
*/
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    /*
    两数相比返回最大值, 此时 Math.max(10, 1);
    默认容量, 由此而来
    */
       return Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   // 不相等的话只有返回当前的 size + 1
   return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
   // 增量, 记录修改/更新次数
   modCount++;  

// 初始化: 10 - 0 > 0
    // 其他: size + 1 > 0
   if (minCapacity - elementData.length > 0)
    // 扩容操作
       grow(minCapacity);
}
private void grow(int minCapacity) {
   // 老的长度, 初始化时为 0,
   int oldCapacity = elementData.length;
   // 新的长度此时 0 + (0 >> 1), newCapacity = 0
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   // 初始化场景: 0 - 10 < 0 ? true newCapacity = 10
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
   // 初始化场景: 10 - 2147483639 > 0 返回 false
   if (newCapacity - MAX_ARRAY_SIZE > 0)
    // 超大长度才可以执行这个方法, 必须大于 MAX_ARRAY_SIZE 一般不会
       newCapacity = hugeCapacity(minCapacity);
   // 复制原数组中的元素, 并扩容
   elementData = Arrays.copyOf(elementData, newCapacity);
}

上看说的是初始化场景,下面看一下其他场景,也是相当简单


private void ensureCapacityInternal(int minCapacity) {
/*
calculateCapacity() 方法, 正常扩容返回 size + 1, 比如 10 + 1, 因为默认长度为 10 当再次新增数据时就会出发扩容
ensureExplicitCapacity() 方法, 调用扩容
*/
   ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
   if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
       return Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   /*
elementData 不等于空数组
返回当前的 size + 1, 即 10 + 1 返回 11
*/
   return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
   // 增量, 记录修改/更新次数
   modCount++;  

// 其他: 11 - 10 > 0 true, 触发扩容, 如果当前下表是 5 的话 5 + 1 =6, 6 < 10 是, 此时不会出发扩容
   if (minCapacity - elementData.length > 0)
    // 扩容操作
       grow(minCapacity);
}
private void grow(int minCapacity) {
   // 老的长度 10
   int oldCapacity = elementData.length;
   // 新的长度此时 10 + (10 >> 1), newCapacity = 15
   int newCapacity = oldCapacity + (oldCapacity >> 1);
   // 15 - 11 < 0 ? false
   if (newCapacity - minCapacity < 0)
       newCapacity = minCapacity;
   // 15 - 2147483639 > 0 返回 false
   if (newCapacity - MAX_ARRAY_SIZE > 0)
    // 超大长度才可以执行这个方法, 必须大于 MAX_ARRAY_SIZE 一般不会
       newCapacity = hugeCapacity(minCapacity);
   // 复制原数组中的元素, 并扩容 newCapacity = 15
   elementData = Arrays.copyOf(elementData, newCapacity);
}

结论

1、 触发扩容的关键是

当前 size + 1 是否大于当前容量,如果大于容量则证明,集合不够用了,需要扩容。如果小与当前容量则证明集合还有容量不需要扩容!

2、 每次扩容的大小是


oldCapacity + (oldCapacity >> 1) 即: 10 + (10 >> 1)

即:当前容量的 1.5 倍!

来源:https://blog.csdn.net/zx2015216856/article/details/82597104

标签:Java,ArrayList,容量,扩容
0
投稿

猜你喜欢

  • 横竖屏切换导致页面频繁重启screenLayout解析

    2021-06-14 05:16:22
  • java按字节截取带有汉字的字符串的解法(推荐)

    2022-10-08 23:49:49
  • Android sdcard实现图片存储 、联网下载

    2023-03-17 22:36:35
  • Android按钮按下的时候改变颜色实现方法

    2021-09-24 20:15:39
  • 英雄联盟辅助lol挂机不被踢的方法(lol挂机脚本)

    2022-03-15 12:57:59
  • Android table布局开发实现简单计算器

    2021-07-15 13:05:28
  • java实现二叉树的创建及5种遍历方法(总结)

    2022-03-14 09:00:28
  • org.slf4j.Logger中info()方法的使用详解

    2021-05-31 20:10:36
  • Java实战之制作在线音乐网站

    2021-11-11 01:36:02
  • JFinal使用ajaxfileupload实现图片上传及预览

    2023-08-05 08:30:48
  • 深入了解C语言的动态内存管理

    2023-09-19 23:46:11
  • UnityShader实现百叶窗效果

    2023-08-22 01:01:11
  • 理解Java当中的回调机制(翻译)

    2023-03-15 04:21:00
  • mybatis 逆向生成后遵循java驼峰法则的解决

    2023-03-03 03:22:02
  • opencv利用视频的前n帧求平均图像

    2021-06-20 11:43:02
  • C#实现在启动目录创建快捷方式的方法

    2021-05-29 13:54:21
  • Android开发实例之多点触控程序

    2023-06-19 00:25:09
  • 关于C#理解装箱与拆箱

    2023-06-18 21:07:50
  • Spring JDBCTemplate原理及使用实例

    2023-03-11 09:47:19
  • Repeater中添加按钮实现点击按钮获取某一行数据的方法

    2022-05-17 08:19:31
  • asp之家 软件编程 m.aspxhome.com