ArrayList

  1. 特点:按添加顺序排列、可重复、非线程安全;
  2. 底层实现:数组
  3. 扩容原理:初始化集合时,默认容量为 0,第一次添加元素时扩容为 10,容量不够时扩容为原来容量的 1.5 倍。

这里扩容指的是无参构造初始化时的场景。对于指定集合长度的构造函数初始化时,初始容量为指定长度,容量不够时再扩容为原来的 1.5 倍。

下面主要介绍无参构造初始化时的场景。

初始化-构造函数

参数定义:

transient Object[] elementData; // 实际存储数据的数组缓冲区

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 初始数组

默认初始化非常简单,调用无参构造器,初始化一个空数组。真正扩容的逻辑在每次添加元素执行。

/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

添加元素-扩容

添加方法:

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

size:ArrayList 的长度,可用 List#size() 获取

添加方法一共做了两件事:

  1. 判断数组容量是否足够,不够则给数组扩容;
  2. 向数组中添加指定的元素;

添加元素不用多说,向数组中的下一个位置插入即可。下面着重介绍容量判断的逻辑:

1. 判断容量大小

首先判断当前集合容量大小是否足够,如果不够就调用扩容方法 grow(int minCapacity)。

// 确保集合可以添加下一个元素 minCapacity:当前需要的最小容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} // 计算添加元素需要的最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果当前集合为空,判断所需最小容量和默认容量大小,返回较大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} // 确保集合可以满足需要的最小容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

2. 扩容方法

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);
}
  1. 旧容量为当前数组长度;
  2. 新容量为旧容量的 1.5 倍;(此处通过位运算计算,右移1位相当于除以 2,再加原来值即扩大1.5倍)
  3. 如果新容量小于所需最小容量,则将新容量赋值为所需最小容量;(这里主要执行场景为集合初始化时,旧容量为0,新容量扩大1.5倍后也为0,这时新容量将被赋值为默认容量 10)
  4. 如果新容量超过最大数组大小(int 最大值 - 8),这时会报内存溢出 或扩容为 int 的最大取值。
  5. 最后将原来数组数据复制到扩容到新容量大小的数组中。

添加元素-实际流程

首先初始化一个 ArrayList,向里面循环添加 11 个元素。下面针对于第一次添加和第11次添加分别查看 数组初始化 和 数组扩容的逻辑。

public static void main(String[] args) {
List<string> list = new ArrayList<>(); for (int i = 0; i < 11; i++) {
list.add("items");
}
}

数组初始化

ArrayList 初始化后,底层会初始化一个空的对象数组 (elementData),长度 (size) 为 0。当第一次添加元素时,会将其初始化为一个长度为 10 的数组。下面看第一次添加元素的流程:

1.进入添加方法 add(E e)

2.判断容量大小

3.计算所需最小容量

当前数组为空数组,所以if 条件成立,DEFAULT_CAPACITY = 10,minCapacity = 1;

当前方法返回 10;

4.判断数组是否扩容

elementData 当前为空数组,length = 0;minCapacity 为上一步返回的 10;所以此处会调用扩容方法 grow()

5.数组扩容(初始化数组)

这里会根据上面指定的默认容量 10 来给数组扩容。

  • oldCapacity = 0;
  • newCapacity = 0;
  • newCapacity = 10;
  • elementData 扩容为容量为10 的新数组

6.添加元素

  • elementData[0] = items
  • size++ = 1;

数组扩容

根据之前程序的运行,集合保存数据的数组在第一次添加元素时扩充容量为 10,所以在第 11 次添加元素时就会调用扩容的逻辑。

1、add() 方法

minCapacity = size + 1 = 11;

2、判断容量大小

3、计算所需最小容量

​ 非第一次 直接返回

4、判断是否扩容

所需最小容量大于数组长度,调用扩容方法。

5、扩容

  • oldCapatity = 10;
  • newCapatity = 15;
  • 拷贝数组内容到一个 容量为 15 的数组中,完成扩容

6、添加元素

  • elementData[10] = e;
  • size ++ = 11;

ArrayList 深入浅出的更多相关文章

  1. Java容器深入浅出之List、ListIterator和ArrayList

    List是Collection接口的子接口,表示的是一种有序的.可重复元素的集合. List接口的主要实现类ArrayList和Vector,底层都是维护了一套动态的,可扩展长度的Object[]数组 ...

  2. 深入浅出!阿里P7架构师带你分析ArrayList集合源码,建议是先收藏再看!

    ArrayList简介 ArrayList 是 Java 集合框架中比较常用的数据结构了.ArrayList是可以动态增长和缩减的索引序列,内部封装了一个动态再分配的Object[]数组 这里我们可以 ...

  3. 深入浅出Struts2+Spring+Hibernate框架

    一.深入浅出Struts2 什么是Struts2? struts2是一种基于MVC的轻量级的WEB应用框架.有了这个框架我们就可以在这个框架的基础上做起,这样就大大的提高了我们的开发效率和质量,为公司 ...

  4. 计算机程序的思维逻辑 (38) - 剖析ArrayList

    从本节开始,我们探讨Java中的容器类,所谓容器,顾名思义就是容纳其他数据的,计算机课程中有一门课叫数据结构,可以粗略对应于Java中的容器类,我们不会介绍所有数据结构的内容,但会介绍Java中的主要 ...

  5. 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...

  6. Scala 深入浅出实战经典 第49课 Scala中Variance代码实战(协变)

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  7. Java编程的逻辑 (38) - 剖析ArrayList

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  8. ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借

    ASP.NET MVC深入浅出系列(持续更新)   一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...

  9. WPF学习之深入浅出话模板

    图形用户界面应用程序较之控制台界面应用程序最大的好处就是界面友好.数据显示直观.CUI程序中数据只能以文本的形式线性显示,GUI程序则允许数据以文本.列表.图形等多种形式立体显示. 用户体验在GUI程 ...

随机推荐

  1. TensorFlow常用Python扩展包

    TensorFlow常用Python扩展包 TensorFlow 能够实现大部分神经网络的功能.但是,这还是不够的.对于预处理任务.序列化甚至绘图任务,还需要更多的 Python 包. 下面列出了一些 ...

  2. TensorRT深度学习训练和部署图示

    TensorRT深度学习训练和部署 NVIDIA TensorRT是用于生产环境的高性能深度学习推理库.功率效率和响应速度是部署的深度学习应用程序的两个关键指标,因为它们直接影响用户体验和所提供服务的 ...

  3. 使用multus实现管理网和业务网分离——calico和flannel共存

    多个网络层面的需求 一开始为k8s集群搭建了calico网络,所有的容器都用calico对应的网卡进行通信.为了实现网络监控的清爽,想把管理组件,例如日志.统计.监控等组件挪到另外一个网络.于是产生一 ...

  4. JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁

    问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...

  5. VRRP协议的原理与配置

    VRRP出现的原因: 局域网中的用户终端通常采用配置一个默认网关的形式访问外部网络,如果此时默认网关设备发生故障,将中断所有用户终端的网络访问,这很可能会给用户带来不可预计的损失. VRRP的优点: ...

  6. .NET平台系列27:在 Linux 上安装 .NET Core/.NET5/.NET6

    系列目录     [已更新最新开发文章,点击查看详细] .NET 在不同的 Linux 发行版上可用. 大多数 Linux 平台和发行版每年都有一个主要版本,并提供用于安装 .NET 的包管理器. 本 ...

  7. 关于win10 samba访问提示用户名和密码错误的原因

    排除掉linux上的配置错误,只需要做到以下两步就可以了: 1.win10系统运行secpol.msc 打开本地安全策略 2.安全策略->本地策略->安全选项 3.右侧找到 网络安全:LA ...

  8. win10下简单截图

    win10 下面可以 win+shift+s 拖动截图,个人感觉是最简单的

  9. JavaScript与服务端进行数据交互的方式

    XMLHttpRequest XHR是项古老的技术,不同的浏览器厂商对其实现方式不同,例如有些浏览器只支持onload事件处理器,有些只支持onreadystatechange事件处理器. 发送Get ...

  10. C# DataGridView单元格画斜线

    功能要求:不符合条件的单元格使用斜线形式表现出来. 1.定义两个变量,一个是存储单元格位置的数组,一个是Graphics 变量 Graphics gdi; List<DataGridViewCe ...