用大白话告诉你ArrayList的底层原理

一、ArrayList的数据结构

ArrayList的底层数据结构就是一个数组,数组元素的类型为Object类型,对ArrayList的所有操作底层都是基于数组的。

二、ArrayList的线程安全性

对ArrayList进行添加元素的操作的时候是分两个步骤进行的,即第一步先在object[size]的位置上存放需要添加的元素;第二步将size的值增加1。由于这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。

具体举例说明:在单线程运行的情况下,如果Size = 0,添加一个元素后,此元素在位置 0,而且Size=1;而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增 加 Size 的值。  那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而Size却等于 2。这就是“线程不安全”了。

如果非要在多线程的环境下使用ArrayList,就需要保证它的线程安全性,通常有两种解决办法:第一,使用synchronized关键字;第二,可以用Collections类中的静态方法synchronizedList();对ArrayList进行调用即可。

三、ArrayList的继承关系

ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。

四、ArrayList的主要成员变量

  • private static final int DEFAULT_CAPACITY = 10;

当ArrayList的构造方法中没有显示指出ArrayList的数组长度时,类内部使用默认缺省时对象数组的容量大小,为10。

  • private static final Object[] EMPTY_ELEMENTDATA = {};

当ArrayList的构造方法中显示指出ArrayList的数组长度为0时,类内部将EMPTY_ELEMENTDATA 这个空对象数组赋给elemetData数组。

  • private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

当ArrayList的构造方法中没有显示指出ArrayList的数组长度时,类内部使用默认缺省时对象数组为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

  • transient Object[] elemetData;

ArrayList的底层数据结构,只是一个对象数组,用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候此字段是不会被序列化的。

  • private int size;

实际ArrayList中存放的元素的个数,默认时为0个元素。

  • private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE – 8;

ArrayList中的对象数组的最大数组容量为Integer.MAX_VALUE – 8。

 1 public class ArrayList<E> extends AbstractList<E>
 2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 3 {
 4     // 版本号
 5     private static final long serialVersionUID = 8683452581122892189L;
 6     // 缺省容量
 7     private static final int DEFAULT_CAPACITY = 10;
 8     // 空对象数组
 9     private static final Object[] EMPTY_ELEMENTDATA = {};
10     // 缺省空对象数组
11     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
12     // 元素数组
13     transient Object[] elementData;
14     // 实际元素大小,默认为0
15     private int size;
16     // 最大数组容量
17     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
18 }

五、ArrayList的构造方法

  • 无参构造方法

对于无参构造方法,将成员变量elementData的值设为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

1 public ArrayList() {
2         // 无参构造函数,设置元素数组为空
3         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
4 }
  • int类型参数构造方法

参数为希望的ArrayList的数组的长度,initialCapacity。首先要判断参数initialCapacity与0的大小关系:

如果initialCapacity大于0,则创建一个大小为initialCapacity的对象数组赋给elementData。

如果initialCapacity等于0,则将EMPTY_ELEMENTDATA赋给elementData。

如果initialCapacity小于0,抛出异常(非法的容量)。

 1 public ArrayList(int initialCapacity) {
 2     if (initialCapacity > 0) { // 初始容量大于0
 3         this.elementData = new Object[initialCapacity]; // 初始化元素数组
 4     } else if (initialCapacity == 0) { // 初始容量为0
 5         this.elementData = EMPTY_ELEMENTDATA; // 为空对象数组
 6     } else { // 初始容量小于0,抛出异常
 7         throw new IllegalArgumentException("Illegal Capacity: "+
 8                                                initialCapacity);
 9     }
10 }
  • Collection<? extends E>类型构造方法

第一步,将参数中的集合转化为数组赋给elementData;

第二步,参数集合是否是空。通过比较size与第一步中的数组长度的大小。

第三步,如果参数集合为空,则设置元素数组为空,即将EMPTY_ELEMENTDATA赋给elementData;

第四步,如果参数集合不为空,接下来判断是否成功将参数集合转化为Object类型的数组,如果转化成Object类型的数组成功,则将数组进行复制,转化为Object类型的数组。

1 public ArrayList(Collection<? extends E> c) { // 集合参数构造函数
2     elementData = c.toArray(); // 转化为数组
3     if ((size = elementData.length) != 0) { // 参数为非空集合
4         if (elementData.getClass() != Object[].class) // 是否成功转化为Object类型数组
5             elementData = Arrays.copyOf(elementData, size, Object[].class); // 不为Object数组的话就进行复制
6     } else { // 集合大小为空,则设置元素数组为空
7         this.elementData = EMPTY_ELEMENTDATA;
8     }
9 }

六、ArrayList的add()方法

在add()方法中主要完成了三件事:首先确保能够将希望添加到集合中的元素能够添加到集合中,即确保ArrayList的容量(判断是否需要扩容);然后将元素添加到elementData数组的指定位置;最后将集合中实际的元素个数加1。

1 public boolean add(E e) { // 添加元素
2     ensureCapacityInternal(size + 1);  // Increments modCount!!
3     elementData[size++] = e;
4     return true;
5 }

七、ArrayList的扩容机制

ArrayList的扩容主要发生在向ArrayList集合中添加元素的时候。由add()方法的分析可知添加前必须确保集合的容量能够放下添加的元素。主要经历了以下几个阶段:

第一,在add()方法中调用ensureCapacityInternal(size + 1)方法来确定集合确保添加元素成功的最小集合容量minCapacity的值。参数为size+1,代表的含义是如果集合添加元素成功后,集合中的实际元素个数。换句话说,集合为了确保添加元素成功,那么集合的最小容量minCapacity应该是size+1。在ensureCapacityInternal方法中,首先判断elementData是否为默认的空数组,如果是,minCapacity为minCapacity与集合默认容量大小中的较大值。

第二,调用ensureExplicitCapacity(minCapacity)方法来确定集合为了确保添加元素成功是否需要对现有的元素数组进行扩容。首先将结构性修改计数器加一;然后判断minCapacity与当前元素数组的长度的大小,如果minCapacity比当前元素数组的长度的大小大的时候需要扩容,进入第三阶段。

第三,如果需要对现有的元素数组进行扩容,则调用grow(minCapacity)方法,参数minCapacity表示集合为了确保添加元素成功的最小容量。在扩容的时候,首先将原元素数组的长度增大1.5倍(oldCapacity + (oldCapacity >> 1)),然后对扩容后的容量与minCapacity进行比较:① 新容量小于minCapacity,则将新容量设为minCapacity;②新容量大于minCapacity,则指定新容量。最后将旧数组拷贝到扩容后的新数组中。

1 private void ensureCapacityInternal(int minCapacity) {
2     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
3         minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
4     }
5
6     ensureExplicitCapacity(minCapacity);
7 }
1 private void ensureExplicitCapacity(int minCapacity) {
2     // 结构性修改加1
3         modCount++;
4     if (minCapacity - elementData.length > 0)
5         grow(minCapacity);
6 }
 1 private void grow(int minCapacity) {
 2     int oldCapacity = elementData.length; // 旧容量
 3     int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
 4     if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
 5         newCapacity = minCapacity;
 6     if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
 7         newCapacity = hugeCapacity(minCapacity); // 指定新容量
 8     // 拷贝扩容
 9     elementData = Arrays.copyOf(elementData, newCapacity);
10 }

八、ArrayList的set(int index,E element)方法

set(int index, E element)方法的作用是指定下标索引处的元素的值。在ArrayList的源码实现中,方法内首先判断传递的元素数组下标参数是否合法,然后将原来的值取出,设置为新的值,将旧值作为返回值返回。

 1 public E set(int index, E element) {
 2     // 检验索引是否合法
 3     rangeCheck(index);
 4     // 旧值
 5     E oldValue = elementData(index);
 6     // 赋新值
 7     elementData[index] = element;
 8     // 返回旧值
 9     return oldValue;
10 }

九、ArrayList的indexOf(Object o)方法

indexOf(Object o)方法的作用是从头开始查找与指定元素相等的元素,如果找到,则返回找到的元素在元素数组中的下标,如果没有找到返回-1。与该方法类似的是lastIndexOf(Object o)方法,该方法的作用是从尾部开始查找与指定元素相等的元素。

查看该方法的源码可知,该方法从需要查找的元素是否为空的角度分为两种情况分别讨论。这也意味着该方法的参数可以是null元素,也意味着ArrayList集合中能够保存null元素。方法实现的逻辑也比较简单,直接循环遍历元素数组,通过equals方法来判断对象是否相同,相同就返回下标,找不到就返回-1。这也解释了为什么要把情况分为需要查找的对象是否为空两种情况讨论,不然的话空对象调用equals方法则会产生空指针异常。

 1 // 从首开始查找数组里面是否存在指定元素
 2 public int indexOf(Object o) {
 3     if (o == null) { // 查找的元素为空
 4         for (int i = 0; i < size; i++) // 遍历数组,找到第一个为空的元素,返回下标
 5             if (elementData[i]==null)
 6                 return i;
 7     } else { // 查找的元素不为空
 8         for (int i = 0; i < size; i++) // 遍历数组,找到第一个和指定元素相等的元素,返回下标
 9             if (o.equals(elementData[i]))
10                     return i;
11     }
12     // 没有找到,返回空
13     return -1;
14 }

十、ArrayList的get(int index)方法

get(int index)方法是返回指定下标处的元素的值。get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0)。如果所引致合法,则调用elementData(int index)方法获取值。在elementData(int index)方法中返回元素数组中指定下标的元素,并且对其进行了向下转型。

1 public E get(int index) {
2     // 检验索引是否合法
3     rangeCheck(index);
4
5     return elementData(index);
6 }

1 E elementData(int index) { 2 return (E) elementData[index]; 3 }

十一、ArrayList的remove(int index)方法

remove(int index)方法的作用是删除指定下标的元素。在该方法的源码中,将指定下标后面一位到数组末尾的全部元素向前移动一个单位,并且把数组最后一个元素设置为null,这样方便之后将整个数组不再使用时,会被GC,可以作为小技巧。而需要移动的元素个数为:size-index-1。

 1 public E remove(int index) {
 2     // 检查索引是否合法
 3     rangeCheck(index);
 4
 5     modCount++;
 6     E oldValue = elementData(index);
 7     // 需要移动的元素的个数
 8     int numMoved = size - index - 1;
 9     if (numMoved > 0)
10         System.arraycopy(elementData, index+1, elementData, index,
11                              numMoved);
12     // 赋值为空,有利于进行GC
13     elementData[--size] = null;
14     // 返回旧值
15     return oldValue;
16 }

十二、ArrayList的优缺点

  • ArrayList的优点

(1)ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快。

(2)ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已。

(3)根据下标遍历元素,效率高。

(4)根据下标访问元素,效率高。

(5)可以自动扩容,默认为每次扩容为原来的1.5倍。

  • ArrayList的缺点

(1)插入和删除元素的效率不高。

(2)根据元素下标查找元素需要遍历整个元素数组,效率不高。

(3)线程不安全。

原文地址:https://www.cnblogs.com/ccff-2016-12-24/p/9498690.html

时间: 2024-11-04 18:40:07

用大白话告诉你ArrayList的底层原理的相关文章

用大白话告诉你啥是Java开发

Java,是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台的总称.用Java实现的HotJava浏览器(支持Java applet)显示了Java的魅力:跨平台.动态的Web.Internet计算.从此,Java被广泛接受并推动了Web的迅速发展,常用的浏览器现均支持Java applet. 经过了多年的发展,Java早已由一门单纯的计算机编程语言,演变为了一套强大的技术体系.是的,什么是Java,我想技术体系四个字应该是最好的概括了吧. 用大白话

用大白话告诉你 :Java 后端到底是在做什么?

阅读本文大概需要 6 分钟. 作者:黄小斜 新手程序员通常会走入一个误区,就是认为学习了一门语言,就可以称为是某某语言工程师了.但事实上真的是这样吗?其实并非如此. 今天我们就来聊一聊,Java 开发工程师到底开发的是什么东西.准确点来说,Java后端到底在做什么? 大家都知道 Java 是一门后端语言,后端指的就是服务端,服务端代码一般运行在服务器上,通常我们运行Java 程序的服务器都是 Linux 服务器. 这些服务器在互联网公司中一般放在一个叫做机房的地方里,于是像我们这类 Java 程

3分钟Tips:用大白话告诉你什么是低耦合|高内聚

1.高内聚 首先我们来看看内聚的含义:软件含义上的内聚其实是从化学中的分子的内聚演变过来的,化学中的分子间的作用力,作用力强则表现为内聚程度高.在软件中内聚程度的高低,标识着软件设计的好坏. 我们在进行架构设计时的内聚高低是指,设计某个模块或者关注点时,模块或关注点内部的一系列相关功能的相关程度的高低. 例如:下单模块: 一般情况下,下单模块都会有如下的信息,订单的信息,产品的信息及谁下的单(买家信息).这是基本的,那么我们设计的时候就要把相关的功能内聚到一起.当然这是从大功能(下单管理)上来说

ArrayList的实现原理--转

1. ArrayList概述: ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小.   每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小.它总是至少等于列表的大小.随着向ArrayList中不断添加元素,其容量也自动增长.自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容

计算机底层原理杂谈(白话文)

简单说一下写这篇文章的缘由.首先这个不是教学类型的,是我Java实在学不下去了,因为好多计算机底层原理都不是很清楚,每次学新东西都由于想不明白底层原理困惑,所以下决心停止学习Java的新东西,开始搞明白底层.一开始搞的所谓的底层是“Java虚拟机”,然后又C语言汇编语言什么的,其实是想图快,尽快接近现在做的事情.后来发现不行,这事快不了,所以干脆就从物理层面用导线灯泡集成芯片开始动手做一个cpu开始吧.其实也没多久,大概三个多月吧,从我之前写的[从零开始自制cpu]系列的学习文章也可以看到开始时

小码哥iOS底层原理班怎么样

众所周知 小码哥的iOS是不错的,对于很多人来说自己从事iOS就是从小码哥开始的,随着发展现阶段的iOS并不如之前那么火热了,那么还有学习iOS的必要呢?回答是肯定的,一句老话:活到老学到老,人想要进步想要更好地生活,那么是离不开学习的.那么现在告诉大家个好消息,iOS底层原理班开班啦!MJ亲授,感兴趣的小伙伴们不要错过了!课程亮点1.市面**iOS高级开发课程,由MJ老师亲自研发,全程精心实力打造.2.为iOS开发者量身打造,深入研究iOS底层的方方面面.掌握了底层,你会发现其他的编程语言.操

Spring Cloud底层原理

目录 一.业务场景介绍 二.Spring Cloud核心组件:Eureka 三.Spring Cloud核心组件:Feign 四.Spring Cloud核心组件:Ribbon 五.Spring Cloud核心组件:Hystrix 六.Spring Cloud核心组件:Zuul 七.总结 概述 毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术.不过大多数讲解还停留在对Spring Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓.因此本文

面试请不要再问我Spring Cloud底层原理

概述 毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术.不过大多数讲解还停留在对Spring Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓.因此本文将通过大量的手绘图,给大家谈谈Spring Cloud微服务架构的底层原理. 实际上,Spring Cloud是一个全家桶式的技术栈,包含了很多组件.本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理.也就是Eureka.Ribbon.Feign.Hystrix.Zuul这几个组件

[转帖]Spring Cloud底层原理

拜托!面试不要再问我Spring Cloud底层原理 https://mp.weixin.qq.com/s/ZH-3JK90mhnJPfdsYH2yDA 毫无疑问,Spring Cloud 是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术. 不过大多数讲解还停留在对 Spring Cloud 功能使用的层面,其底层的很多原理,很多人可能并不知晓. 因此本文将通过大量的手绘图,给大家谈谈 Spring Cloud 微服务架构的底层原理. 实际上,Spring Cloud 是一个全家桶式的