简介

  来源:博客园    作者:吾王彦

  博客链接:https://www.cnblogs.com/qinjunlin/p/13724987.html

  ArrayList动态数组,是 java 中比较常用的数据结构。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。本随笔主要讲述ArrayList的扩容机制以及它的底层实现。如果懒得看整个分析过程,你可以直接查看文章最后的总结

成员变量

 1    private static final int DEFAULT_CAPACITY = 10;  //默认的初始容量为10
2
3 private static final Object[] EMPTY_ELEMENTDATA = {}; //空数组
4
5 /**
6 *与默认的初始容量DEFAULT_CAPACITY区分开来,
7 *简单来讲就是第一次添加元素时知道该 elementData
8 *从空的构造函数还是有参构造函数被初始化的。以便确认如何扩容。
9 **/
10 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//空数组,与EMPTY_ELEMENTDATA空数组区分
11
12 /**
13 * 存储ArrayList元素的数组缓冲区。
14 * ArrayList的容量是这个数组缓冲区的长度。
15 *任何空的ArrayList 即 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
16 *当添加第一个元素时,将扩展为DEFAULT_CAPACITY。
17 **/
18 transient Object[] elementData;
19
20 private int size; //实际元素个数

构造函数

有参构造

1 /**
2 * 构造一个初始容量为10的空ArrayList
3 */
4 public ArrayList() {
5 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
6 }

这里需要注意,注释(是我翻译后的)是说构造一个容量大小为 10 的空的 list 数组,但这里构造函数了只是给 elementData 赋值了一个空的数组,实际上并未开始扩容,这时候的容量还是0,真正开始扩容其实是在第一次添加元素时才容量扩大至 10 的。

有参构造函数

下面的代码是构造一个大小为 initialCapacity 的 ArrayList

 1 public ArrayList(int initialCapacity) {
2 if (initialCapacity > 0) { //当 initialCapacity 大于零时初始化一个大小为 initialCapacity 的 object 数组并赋值给 elementData
3 this.elementData = new Object[initialCapacity];
4 } else if (initialCapacity == 0) { //当 initialCapacity 为零时则是把 空数组EMPTY_ELEMENTDATA 赋值给 elementData
5 this.elementData = EMPTY_ELEMENTDATA;
6 } else {
7 throw new IllegalArgumentException("Illegal Capacity: "+
8 initialCapacity);
9 }
10 }

使用指定 Collection 来构造 ArrayList 的构造函数。将 Collection 转化为数组并赋值给 elementData。

 1 public ArrayList(Collection<? extends E> c) {
2 elementData = c.toArray();
3 if ((size = elementData.length) != 0) {
4 // c.toArray might (incorrectly) not return Object[] (see 6260652)
5 if (elementData.getClass() != Object[].class) //判断 elementData 的 class 类型是否为 Object[],不是的话则做一次转换
6 elementData = Arrays.copyOf(elementData, size, Object[].class);//
7 } else {
8 // size 为零,则将空数组赋给elementData,相当于new ArrayList(0)
9 this.elementData = EMPTY_ELEMENTDATA;
10 }
11 }

产生扩容的操作

add方法

 1 public boolean add(E e) {
2 ensureCapacityInternal(size + 1); //每次添加元素到集合中时都会先确认下集合容量大小
3 elementData[size++] = e; //size 自增 +1。
4 return true;
5 }
6
7 //确认容量大小
8 private void ensureCapacityInternal(int minCapacity) {
9 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
10 }
11
12 //返回容量大小
13 private static int calculateCapacity(Object[] elementData, int minCapacity) {
14 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
15 return Math.max(DEFAULT_CAPACITY, minCapacity); //这里其实才是真正的开始初始化数组的容量大小为10
16 }
17 return minCapacity;
18 }
19
20 //根据确认的容量的大小判断是否进行扩容
21 private void ensureExplicitCapacity(int minCapacity) {
22 modCount++; //记录操作次数
23
24 // overflow-conscious code
25 if (minCapacity - elementData.length > 0)
26 grow(minCapacity); //调用扩容方法
27 }

由上面的源代码可知数组的容量大小的初始化是在第一次添加元素的时候才开始的。calculateCapacity方法中有一个判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA   ,在第一次添加元素的时候,集合本身就是空的,所以会 就取 DEFAULT_CAPACITY minCapacity 的最大值也就是 10。

初始化完容量后,之后的每次添加元素都会走一遍上面代码的流程,就是每次添加元素前都会确认容量,然后根据minCapacity是否大于 elementData 的长度来决定是否对集合进行扩容。

grow扩容

 1 //根据确认的容量的大小判断是否进行扩容
2 private void ensureExplicitCapacity(int minCapacity) {
3 modCount++; //记录操作次数
4
5 // overflow-conscious code
6 if (minCapacity - elementData.length > 0)
7 grow(minCapacity); //调用扩容方法
8 }
9
10 //扩容函数
11 private void grow(int minCapacity) {
12 // overflow-conscious code
13 int oldCapacity = elementData.length;
14 int newCapacity = oldCapacity + (oldCapacity >> 1); //这里NewCapacity为原来容量的1.5倍
15 if (newCapacity - minCapacity < 0)
16 newCapacity = minCapacity;
17 if (newCapacity - MAX_ARRAY_SIZE > 0)
18 newCapacity = hugeCapacity(minCapacity);
19 // minCapacity is usually close to size, so this is a win:
20 elementData = Arrays.copyOf(elementData, newCapacity);
21 }
1 //扩容过大,或者所需容量过大则调用此方法
2 private static int hugeCapacity(int minCapacity) {
3 if (minCapacity < 0) // overflow
4 throw new OutOfMemoryError();
5 return (minCapacity > MAX_ARRAY_SIZE) ?
6 Integer.MAX_VALUE :
7 MAX_ARRAY_SIZE;
8 }

有代码可知,这里的扩容是扩容至原来容量的 1.5 倍。为什么是1.5倍呢?可以看看下面的这个

以无参构造为例。

int oldCapacity = elementData.length;  //原容量为10,二进制表示为1010

int newCapacity = oldCapacity + (oldCapacity >> 1);   //此处将原来的1010右移一位,变成101,即10进制的5

所以 newCapacity  = 10 + 5,即是15 。所以1.5倍是这样来的。

可以理解“>>”右移符号相当于除以2

其次,扩容1.5并不一定是最终的容量大小。因为还有两个判断。

1、如果1.5倍太小的话,则将我们所需的容量大小赋值给newCapacity

2、1.5倍太大或者我们需要的容量太大,则调用 hugeCapacity方法。

太大究竟是多大?请看下面

hugeCapacity方法。中的参数分析

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //ArrayList的成员变量
Integer.MAX_VALUE的大小为2的31次-1,即2147483647

所以你说大不大?嘻嘻

总结

  首先,定义一个ArrayList,常用的有两种方式。一种是使用无参构造,即new ArrayList();另一种是使用有参构造,new ArrayList(6),new一个初始容量大小为6的ArrayList。

  使用无参构造定义的ArrayList,在没有添加元素之前,初始容量为0,只有第一次添加元素的时候才会初始化初始容量为10,每次添加元素前都会确认集合的容量大小

如果容量不够用会进行扩容。一般会扩容至原来的1.5倍,如果所需容量太大,会扩容到2的31次-1,或者(2的31次-1)-8(详细看上面代码分析)。

  使用有参构造定义ArrayList,初始容量大小就是有参定义的大小,new ArrayList(6) ,初始容量就是6 。扩容机制有参无参构造都是一样的。

  看到这里,你是否还有个疑问,为什么是设计成扩容1.5倍呢?因为,因为他喜欢啊。哈哈哈哈哈。。。。。。。

JDK1.6 ArrayList无参构造,默认容量是10

JDK1.7 ArrayList无参构造,默认容量才改为是0,只有在add()时才初始化为10

这里讨论的是JDK1.8的版本的



ArrayList扩容机制以及底层实现的更多相关文章

  1. ArrayList扩容机制

    一.先从 ArrayList 的构造函数说起 ArrayList有三种方式来初始化,构造方法源码如下: 1 /** 2 * 默认初始容量大小 3 */ 4 private static final i ...

  2. ArrayList扩容机制实探

    ArrayList初始化 问题:执行以下代码后,这个list的列表大小(size)和容量(capacity)分别是多大? List<String> list = new ArrayList ...

  3. java集合专题 (ArrayList、HashSet等集合底层结构及扩容机制、HashMap源码)

    一.数组与集合比较 数组: 1)长度开始时必须指定,而且一旦指定,不能更改 2)保存的必须为同一类型的元素 3)使用数组进行增加/删除元素-比较麻烦 集合: 1)可以动态保存任意多个对象,使用比较方便 ...

  4. ArrayList源码解析(二)自动扩容机制与add操作

    本篇主要分析ArrayList的自动扩容机制,add和remove的相关方法. 作为一个list,add和remove操作自然是必须的. 前面说过,ArrayList底层是使用Object数组实现的. ...

  5. ArrayList的扩容机制

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

  6. 【数组】- ArrayList自动扩容机制

    不同的JDK版本的扩容机制可能有差异 实验环境:JDK1.8 扩容机制: 当向ArrayList中添加元素的时候,ArrayList如果要满足新元素的存储超过ArrayList存储新元素前的存储能力, ...

  7. 关于ArrayList的扩容机制

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

  8. Java ArrayList自动扩容机制

    动态扩容 1.add(E e)方法中 ①  ensureCapacityInternal(size+1),确保内部容量,size是添加前数组内元素的数量 ②  elementData[size++] ...

  9. 浅谈 ArrayList 及其扩容机制

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

随机推荐

  1. Linux-基础命令学习

    Linux终端 Linux存在两种终端模拟器,一种类MAC的Gnome和一种类Win的KDE 远程连接工具: xshell,putty,crt(网工) 如果在Linux下输入tty 1 wang@DE ...

  2. Redis 使用入门

    NoSql概述 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",它泛指非关系型的数据库, Redis 是一个高性能的开源的.C语言写的Nosql( ...

  3. C++的标识符的作用域与可见性

    下面是关于C++的标识符的作用域与可见性学习记录,仅供参考 标识符的作用域与可见性 作用域是一个标识符在程序正文中有效的区域. 作用域分类 ①函数原型作用域 ②局部作用域(快作用域) ③类作用域 ④文 ...

  4. java基础知识 + 常见面试题

    准备校招面试之Java篇 一. Java SE 部分 1.1 Java基础 1. 请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的? Object 的 hash ...

  5. HDOJ-3065(AC自动机+每个模板串的出现次数)

    病毒侵袭持续中 HDOJ-3065 第一个需要注意的是树节点的个数也就是tree的第一维需要的空间是多少:模板串的个数*最长模板串的长度 一开始我的答案总时WA,原因是我的方法一开始不是这样做的,我是 ...

  6. 无需编程,通过配置零代码生成CRUD RESTful API

    Hello,crudapi!(你好,增删改查接口!) 本文通过学生对象为例,无需编程,通过配置实现CRUD RESTful API. 概要 CRUD简介 crud是指在做计算处理时的增加(Create ...

  7. FreeBSD 13 显卡支持

    On FreeBSD 13, using drm-devel-kmod, support is the same as on Linux 5.4. This includes support for ...

  8. PHP配置 2. 日志相关配置

    例如,在disable_functions,定义禁用phpinfo函数, # vim /usr/local/php/etc/php.ini disable_functions=phpinfo,eval ...

  9. Memory Networks01 记忆网络经典论文

    目录 1.Memory Networks 框架 流程 损失函数 QA 问题 一些扩展 小结 2.End-To-End Memory Networks Single Layer 输入模块 算法流程 Mu ...

  10. idea自制模板代码快捷键

    一:/** 方法描述 /*** 功能描述: * @param: $param$ * @return: $return$ * @auther: $user$ * @date: */二./c** 类描述 ...