浅谈ArrayList

  ArrayList类又称动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问。但因为ArrayList中插入或删除一个元素需要移动其他元素,所以不适合在插入和删除操作频繁的场景下使用。

  ArrayList的容量可以随着元素的增加而自动增加,因此不用担心ArrayList容量不足的问题。

  ArrayList是非线程安全的。

  接下来,我们将解析ArrayList的构造方法,在看构造方法之前,我们先来明确一下ArrayList源码中的一些概念。这些变量和对象大家先记住就好了,后面会看到它们的用途。

// 默认的容量大小(常量)
private static final int DEFAULT_CAPACITY = 10; // 定义的空数组(final修饰,大小固定为0)
private static final Object[] EMPTY_ELEMENTDATA = {}; // 定义的默认空容量的数组(final修饰,大小固定为0)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 定义的不可被序列化的数组,实际存储元素的数组
transient Object[] elementData; // 数组中元素的个数
private int size;

  ArrayList有三种构造方法:

    1.无参的构造方法

    2.根据传入的数值大小,创建指定长度的数组

    3.通过传入Collection元素列表进行生成

1.无参的构造方法

// 无参的构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

  可以看出来,当我们直接创建ArrayList时,elementData被赋予了默认空容量的数组。注意,因为默认空容量数组是被final修饰的,此时ArrayList数组是空的、固定长度的,也就是说其容量此时是0,元素个数size为默认值0。

2.根据传入的数值大小,创建指定长度的数组

// 传容量的构造方法
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}

  当initialCapacity > 0时,会在堆上new一个大小为initialCapacity的数组,然后将其引用赋给elementData,此时ArrayList的容量为initialCapacity,元素个数size为默认值0。

  当initialCapacity = 0时,elementData被赋予了默认空数组,因为其被final修饰了,所以此时ArrayList的容量为0,元素个数size为默认值0。

  当initialCapacity < 0时,会抛出异常。

3.通过传入Collection元素列表进行生成

// 传入Collection元素列表的构造方法
public ArrayList(Collection<? extends E> c) {
// 将列表转化为对应的数组
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 此处见下面详细解析
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// 赋予空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

  传入Collection元素列表后,构造方法首先会将其转化为数组,将其索引赋给elementData。如果此数组的长度为0,会重新赋予elementData为空数组,此时ArrayList的容量是0,元素个数size为0。

  如果数组的长度不为0,会跟新size的大小为其长度,也就是元素的个数,然后执行里面的程序。大家对里面的代码可能不理解,让我们等会看下面解析。执行完后此时ArrayList的容量为传入序列的长度,也就是size的大小,同时元素个数也为size,也就是说,此时ArrayList是满的。

  让我们来看看下面的代码,然后再去理解上面 if 语句的代码:

public class Test {
public static void main(String[] args) {
// 1.创建Student对象数组
Student[] students = new Student[] {
new Student("小明", 18),
new Student("小李", 19),
new Student("小张", 21)
}; // 2.将其赋值给Object对象数组
Object[] objects = students; // 3.执行if语句前,打印数组的class
System.out.println("执行前:" + objects.getClass()); // 4.执行上面的代码
if (objects.getClass() != Object[].class) {
objects = Arrays.copyOf(objects, objects.length, Object[].class);
} // 5.执行if语句后,打印数组的class
System.out.println("执行后:" + objects.getClass());
}
}

  程序的运行结果如下:

  可以看到,对象数组也是有.class的,其中含有所存储元素的类型,而上面的那段代码的作用就是将原对象数组的数组类型转化为Object对象数组的数组类型,以便更好的存储。

ArrayList的扩容机制

  当我们探讨扩容时,肯定要从ArrayList的add方法走起,让我们来看看吧。

public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}

  这是最基本的add方法,当然,也是可以来说明问题的。可以看到,此add方法的参数就是一个被加元素,moCount是记录ArrayList被修改的次数的,可以不用管。然后是另一个add方法,所传的值是被加元素、当前数组和当前数组的元素个数,让我们来看看这个add方法吧。

private void add(E e, Object[] elementData, int s) {
// 判断元素个数是否等于当前容量
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}

  首先,它判断了元素个数是否等于当前的容量,也就是判断当前数组空间是不是满的,如果二者相等,则当前空间是满的,就需要扩容了,grow函数就是扩容函数了,扩容后再将被加元素加到数组中。

  下面我们来看看grow函数是什么样子的:

private Object[] grow() {
return grow(size + 1);
}

  它里面调用了一个带参的grow函数,参数是当前元素个数+1,也就是当前容量+1。返回的是这个函数的返回值,让我们进一步研究。

private Object[] grow(int minCapacity) {
// 获取老容量,也就是当前容量
int oldCapacity = elementData.length;
// 如果当前容量大于0 或者 数组不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
// 如果是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,创建新数组
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}

  首先,它是记录了一下老容量的大小,然后再进行下面的操作。

  如果当前容量大于0,或者当前数组不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,前面说明过,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的长度固定为0的数组,在三个构造方法中,只有无参构造方法中elementData被赋予了DEFAULTCAPACITY_EMPTY_ELEMENTDATA。也就是说,这个if语句中不会处理用默认无参构造方法创建的数组的初始扩容情况,那么其余的情况都是由此if语句来处理的。

  我们来看一下if里面的操作,先创建一个新的数组,然后将旧数组拷贝到新数组并赋给elementData返回。ArraysSupport.newLength函数的作用是创建一个大小为oldCapacity+max(minimum growth, preferred growth)的数组。minCapacity是传入的参数,我们上面看过,它的值是当前容量(老容量)+1,那么minCapacity - oldCapacity的值恒为1,minimum growth的值恒为1;oldCapacity >> 1的功能是将oldCapacity 进行位操作,右移一位,也就是减半,preferred growth的值为oldCapacity 的一半。

  当oldCapacity 为0时,右移后还是0,也就是说此时扩容的大小为0+max(1,0)=1,容量从0扩展到1,那么什么时候是这种情况呢?当传容量的构造方法传入的是0时,elementData被赋予的是EMPTY_ELEMENTDATA,数组容量为0,此时添加元素时,符合if的条件,会进入此扩容情况,容量从0扩展到1。当传Collection元素列表的构造方法被传入空列表时,elementData被赋予的是EMPTY_ELEMENTDATA,数组容量为0,此时添加元素时,符合if的条件,会进入此扩容情况,容量从0扩展到1。

  当oldCapacity 大于0时,新创建的数组大小是老容量+老容量的一半,也就是老容量的1.5倍,每次扩容到原来的1.5倍。

  if之外就剩一种情况了,也就是用默认无参构造方法创建的数组的初始扩容情况。此时的容量为0,添加一个元素时会创建一个新的数组,其大小为max(DEFAULT_CAPACITY, minCapacity),我们从上面的源码变量信息中可得知DEFAULT_CAPACITY的值为10,而minCapacity的值为1,所以添加一个元素时,max(DEFAULT_CAPACITY, minCapacity)的值必为10。也就是说,当我们用默认无参构造方法创建的数组在添加元素前,ArrayList的容量为0,添加一个元素后,ArrayList的容量为10。

总结一下

ArrayList的特点:

  1.ArrayList的底层数据结构是数组,所以查找遍历快,增删慢。

  2.ArrayList可随着元素的增长而自动扩容,正常扩容的话,每次扩容到原来的1.5倍。

  3.ArrayList的线程是不安全的。

ArrayList的扩容:

  在两种情况下需要扩容:

  第一种情况,当ArrayList的容量为0时,此时添加元素的话,需要扩容。什么时候容量可能为0呢?当然是刚被创建的时候,三种构造方法创建的ArrayList在扩容时略有不同:

    (1)无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,此后若需要扩容,则正常扩容。

    (2)传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。

    (3)传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。

  第二种情况,当ArrayList的容量大于0时,此时添加元素若需要扩容的话,正常扩容,每次扩容到原来的1.5倍。

浅谈 ArrayList 及其扩容机制的更多相关文章

  1. 浅谈ArrayList

    浅谈ArrayList 废话不多说(事实是不会说),让我们直接进入正题 首先讲一讲最基本的ArrayList的初始化,也就是我们常说的构造函数,ArrayList给我们提供了三种构造方式,我们逐个来查 ...

  2. ArrayList的扩容机制

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

  3. 关于ArrayList的扩容机制

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

  4. 浅谈Java的反射机制和作用

    浅谈Java的反射机制和作用 作者:Java大师 欢迎转载,转载请注明出处 很多刚学Java反射的同学可能对反射技术一头雾水,为什么要学习反射,学习反射有什么作用,不用反射,通过new也能创建用户对象 ...

  5. 浅谈:Redis持久化机制(一)RDB篇

    浅谈:Redis持久化机制(一)RDB篇 ​ 众所周知,redis是一款性能极高,基于内存的键值对NoSql数据库,官方显示,它的读效率可达到11万次每秒,写效率能达到8万次每秒,因为它基于内存以及存 ...

  6. 浅谈:Redis持久化机制(二)AOF篇

    浅谈:Redis持久化机制(二)AOF篇 ​ 上一篇我们提及到了redis的默认持久化方式RDB,是一种通过存储快照数据方式持久化的机制,它在宕机后会丢失掉最后一次更新RDB文件后的数据,这也是由于它 ...

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

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

  8. 浅谈C语言中断处理机制

    一.中断机制 1.实现中断响应和中断返回 当CPU收到中断请求后,能根据具体情况决定是否响应中断,如果CPU没有更急.更重要的工作,则在执行完当前指令后响应这一中断请求.CPU中断响应过程如下:首先, ...

  9. Java ArrayList自动扩容机制

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

随机推荐

  1. Linux基础 Day1

    Linux-Day1 1.用户登录 root用户 是一个特殊的管理账号,也可以称为超级管理员 root用户对系统有完全控制的权限 对系统的损害会无限大 在工作中,如果没有特殊的必要,尽量不要使用roo ...

  2. C++ Templates (1.1 初窥函数模板 A First Look at Function Templates)

    返回完整目录 目录 1.1 初窥函数模板 A First Look at Function Templates 1.1.1 定义模板 Defining the Template 1.1.2 使用模板 ...

  3. HM16.0之帧间预测——xCheckRDCostInter()函数

    参考:https://blog.csdn.net/nb_vol_1/article/category/6179825/1? 1.源代码: #if AMP_MRG Void TEncCu::xCheck ...

  4. Java 实例 - 查看当前工作目录

    package guyu.day0820; /** * @Author: Fred * @Date: 2020/8/20 14:25 */ public class Demo03 { public s ...

  5. Vue企业级优雅实战-00-开篇

    从2018.1.开始参与了多个企业的中台建设,这些中台的技术选型几乎都是基于 Spring Cloud 微服务架构 + 基于 Vue 全家桶的前端.我前后端架构及开发我几乎各占一半的精力,在企业级前端 ...

  6. 【转】Echarts自适应

    var myChart1 = echarts.init(document.getElementById('chart1')); var option = myChart1.getOption(); w ...

  7. vue路由守卫+cookie实现页面跳转时验证用户是否登录----(二)设置路由守卫

    上一篇我们已经封装好了cookie方法,登录成功之后也可以吧用户信息存到cookie中,接下来需要在router/index.js中引入一下cookie.js文件 然后继续添加以下代码 /* * be ...

  8. 极简 Node.js 入门 - 3.5 文件夹操作

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  9. OC基础--数据类型与表达式

    前言 做iOS开发有3年了,从当初的小白到现在,断断续续看过很多资料,之前也写过一些博文来记录,但是感觉知识点都比较凌乱.所以最近准备抽时间把iOS开发的相关知识进行一个梳理,主要分为OC基础.UI控 ...

  10. P4719 【模板】"动态 DP"&动态树分治

    题目描述 给定一棵 n 个点的树,点带点权. 有 m 次操作,每次操作给定 x,y,表示修改点 x 的权值为 y. 你需要在每次操作之后求出这棵树的最大权独立集的权值大小. 输入格式 第一行有两个整数 ...