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. 3D点云点云分割、目标检测、分类

    3D点云点云分割.目标检测.分类 原标题Deep Learning for 3D Point Clouds: A Survey 作者Yulan Guo, Hanyun Wang, Qingyong H ...

  2. OpenFeign远程调用原理

    之前对OpenFeign 了解到只用在接口上面打个注解,然后就可以通过内部调用去调用远程地址.研究完Feign生成对象以及代理对象的作用过程之后发现这个过程用到了Spring的好多东西,在之后的过程中 ...

  3. 用Java如何设计一个阻塞队列,然后说说ArrayBlockingQueue和LinkedBlockingQueue

    前言 用Java如何设计一个阻塞队列,这个问题是在面滴滴的时候被问到的.当时确实没回答好,只是说了用个List,然后消费者再用个死循环一直去监控list的是否有值,有值的话就处理List里面的内容.回 ...

  4. 并发王者课-铂金2:豁然开朗-“晦涩难懂”的ReadWriteLock竟如此妙不可言

    欢迎来到<并发王者课>,本文是该系列文章中的第15篇. 在上篇文章中,我们介绍了Java中锁的基础Lock接口.在本文中,我们将介绍Java中锁的另外一个重要的基本型接口,即ReadWri ...

  5. 09:CBV与settings

    CBV源码 # 切入点 url(r'^login/', views.Mylogin.as_view()) '''类名点名字还加括号 名字要么是绑定给类的方法 要么是无参函数''' ​ 1.as_vie ...

  6. 深入了解Debug和Release的区别

    原文地址:https://blog.csdn.net/sky___ice/article/details/8993885 一: Bin 目录用来存放编译的结果,bin是二进制binrary的英文缩写, ...

  7. [源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构

    [源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构 目录 [源码解析] 深度学习分布式训练框架 horovod (6) --- 后台线程架构 0x00 摘要 0x01 ...

  8. Map集合中的方法

    Map集合中的方法: values() 方法 : 获取集合中的所有的值----没有键,没有对应关系, KeySet() 方法: 将Map中所有的键存入到set集合中.因为set具备迭代器.所有可以迭代 ...

  9. Linux中mail的用法

    简介:mail命令是命令行的电子邮件发送和接收工具.操作的界面不像elm或pine那么容易使用,但功能非常完整Red Hat上sendmail服务一般是自动启动的.可以通过下面的命令查看sendmai ...

  10. [Usaco2018 Dec]Teamwork 题解

    题目描述 题目描述 在Farmer John最喜欢的节日里,他想要给他的朋友们赠送一些礼物.由于他并不擅长包装礼物,他想要获得他的 奶牛们的帮助.你可能能够想到,奶牛们本身也不是很擅长包装礼物,而Fa ...