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. CVPR2020论文解读:手绘草图卷积网络语义分割

    CVPR2020论文解读:手绘草图卷积网络语义分割 Sketch GCN: Semantic Sketch Segmentation with Graph Convolutional Networks ...

  2. 自动机器学习(AutoML)

    自动机器学习(AutoML) 不再需要苦恼于学习各种机器学习的算法 目录: 一.为什么需要自动机器学习 二.超参数优化 Hyper-parameter Optimization 三.元学习 Meta ...

  3. 定位服务API案例

    定位服务API案例 要使用定位服务API,需要确保设备已经下载并安装了HMS Core服务组件,并将Location Kit的SDK集成到项目中. 指定应用权限 Android提供了两种位置权限: A ...

  4. liunx:网络命令

    现系统的学习一下Web渗透相关的命令 ping ping 命令是用来测试TCP/IP 网络是否畅通或者测试网络连接速度的命令,对确定网络是否正确连接,以及网络连接的状况十分有用.简单的说,ping就是 ...

  5. yum的配置

    1. 创建两台虚拟机[root@room9pc01 ~]# clone-vm7Enter VM number: 8 [root@room9pc01 ~]# clone-vm7Enter VM numb ...

  6. linux安装后配置

    1.1 系统设置(自测用,公司不需要) 1.1.1 Selinux系统安全保护 Security-Enhanced Linux – 美国NSA国家安全局主导开发,一套增强Linux系统安 全的强制访问 ...

  7. Webflux(史上最全)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  8. 带你了解Java的序列化与反序列化

    什么是序列化 序列化:将 Java 对象转换成字节流的过程. 什么是反序列化 反序列化:将字节流转换成 Java 对象的过程. 序列化的实现 当 Java 对象需要在网络上传输 或者 持久化存储到文件 ...

  9. 详解 CDN 加速

    背景 本来是为了深入了解 CDN 的,结果发现前置知识:IP.域名.DNS 都还不算特别熟,所以先写了他们 现在终于来聊一聊 CDN 啦 本文素材均出自:https://www.bilibili.co ...

  10. Java并发之ReentrantLock源码解析(四)

    Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...