1. 是什么

底层由数组实现的,可扩容的顺序表

有序、可以重复

2. 如何使用

public class ArrayListTest
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2"); System.out.println(list); list.remove(0);
list.remove("2"); }
}

3. 原理分析

3.1. uml



可以看出ArrayList是个List、可克隆、可序列化、可以使用下标访问

3.2. 构造方法

使用object数组,并且初始化长度为0

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//如果使用默认的无参构造初始容量为0,第一次扩容时容量为10
private static final int DEFAULT_CAPACITY = 10; //没有元素时使用空数组,两者区别是啥?
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //List底层使用Object数组实现
transient Object[] elementData; //Object数组中实际使用的大小
private int size; public ArrayList() {
//默认空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
}

3.3. add方法

  • 不扩容O(1),扩容O(N)
public boolean add(E e) {
//确保容量足够
ensureCapacityInternal(size + 1); // Increments modCount!!
//在默认赋值
elementData[size++] = e;
return true;
}

3.3.1. 确保容量足够容纳新的元素

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} private static int calculateCapacity(Object[] elementData, int minCapacity) {
//使用默认的无参构造方法创建的容量为0,那么第一次扩容为10个
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);
} private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新长度=原长度*1.5
int newCapacity = oldCapacity + (oldCapacity >> 1);
//新长度<最小需要的长度,那么取最小需要的长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //新长度比数组最大限制长度还大private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
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);
} private static int hugeCapacity(int minCapacity) {
//最小需要的长度也溢出了,OOM
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//最小需要的长度比数组最大限制长度大则使用Integer.MAX_VALUE,否则使用数组最大限制长度
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

3.3.2. 把元素放入数组最后一个位置

//没啥好说的,赋值然后下标+1
elementData[size++] = e;

3.4. remove方法【按下标删除元素】

  • O(N)
public E remove(int index) {
//防止下标越界
rangeCheck(index); modCount++;
//保存要删除的数以便返回
E oldValue = elementData(index); //需要移动的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//后面的往前移
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//其实数组大小没有变化。当然size属性必须更新的
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

3.4.1. 把数组index位置之后的数据往前挪

//计算index后面需要移动的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//后面的往前移
System.arraycopy(elementData, index+1, elementData, index,
numMoved);

3.4.2. 更新size【数组不缩容】

 //其实数组大小没有变化。当然size属性必须更新的
elementData[--size] = null; // clear to let GC do its work

3.5. remove方法【按元素内容删除】

  • O(N)
public boolean remove(Object o) {
//为null
if (o == null) {
//找到下标
for (int index = 0; index < size; index++)
//==null判断
if (elementData[index] == null) {
//删除该下标的元素
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
//equals判断
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

3.5.1. 首先找到要删除的元素的下标

for (int index = 0; index < size; index++)
{
//...
}

如上无非就时遍历查找,效率O(N),然后按照下标删除,如下

  • fastRemove
 private void fastRemove(int index) {
modCount++;
//计算移动元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//复制到前面
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//赋为null,并且size--
elementData[--size] = null; // clear to let GC do its work
}

这段代码同上面的remove方法【按下标删除元素】的逻辑

3.5.2. 把数组index位置之后的数据往前挪

//计算移动元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//复制到前面
System.arraycopy(elementData, index+1, elementData, index,
numMoved);

3.5.3. 更新size【数组不缩容】

//赋为null,并且size--
elementData[--size] = null; // clear to let GC do its work

4. ConcurrentModificationException

参考:fail-fast.md

public class ConcurrentModificationExceptionTest
{
public static void main(String[] args)
{
List<String> stringList = new ArrayList<>();
for (int i = 0; i < 1000; i++)
{
stringList.add(String.valueOf(i));
} for (String s : stringList)
{
stringList.remove(s);//java.util.ConcurrentModificationException
}
}
}

如上的代码运行过程中会抛出ConcurrentModificationException异常

4.1. 原因分析

遍历时(get)执行删除操作(remove)会抛出这个异常,这叫做fast-fail机制

这是modCount和expectedCount不相等导致的

4.2. 解决

使用fail-safe的JUC包下的CopyOnWriteArrayList

4.Java SDK源码分析系列笔记-LinkedList的更多相关文章

  1. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  2. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  3. [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat

    概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...

  4. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  5. spring源码分析系列 (8) FactoryBean工厂类机制

    更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...

  6. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

  7. spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor

    更多文章点击--spring源码分析系列 主要分析内容: 一.BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor简述与demo示例 ...

  8. spring源码分析系列 (3) spring拓展接口InstantiationAwareBeanPostProcessor

    更多文章点击--spring源码分析系列 主要分析内容: 一.InstantiationAwareBeanPostProcessor简述与demo示例 二.InstantiationAwareBean ...

  9. spring源码分析系列 (2) spring拓展接口BeanPostProcessor

    Spring更多分析--spring源码分析系列 主要分析内容: 一.BeanPostProcessor简述与demo示例 二.BeanPostProcessor源码分析:注册时机和触发点 (源码基于 ...

  10. Spring IOC 容器源码分析系列文章导读

    1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...

随机推荐

  1. 元模型对AI的哲学意义:让机器真正"懂"世界

    元模型对AI的哲学意义:让机器真正"懂"世界 (用日常语言和比喻解释) 1. 传统AI像"死记硬背的学生" 问题:现在的ChatGPT就像背了无数词典的人,能对 ...

  2. zk基础—5.Curator的使用与剖析

    大纲 1.基于Curator进行基本的zk数据操作 2.基于Curator实现集群元数据管理 3.基于Curator实现HA主备自动切换 4.基于Curator实现Leader选举 5.基于Curat ...

  3. bat脚本之启动MySQL服务

    @echo off :: 获取管理员权限 %1 mshta vbscript:CreateObject("Shell.Application").ShellExecute(&quo ...

  4. CF1627B题解

    Not Sitting 题面翻译 Rahul 和 Tina 在玩一个游戏.游戏在一个 n×mn\times mn×m 的网格图上进行,记第 rrr 行第 ccc 列上的格子为 (r,c)(r,c)(r ...

  5. 痞子衡嵌入式:在含多个i.MXRT的主从系统中共享一颗Flash启动的方法与实践(上篇)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是多个i.MXRT共享一颗Flash启动的方法. 有些特殊的客户应用会采用多颗 i.MXRT 芯片设计一主多从的硬件架构(目的不一,或仿多 ...

  6. Java Solon-MCP 实现 MCP 实践全解析:SSE 与 STDIO 通信模式详解

    参文参考自:https://blog.csdn.net/lingding_cn/article/details/147355620 一.MCP简介 MCP(Model Context Protocol ...

  7. 操作系统:Linux -- 详解socket实现与网络编程接口

    现在我们来瞧一瞧 Linux 的网络程序,不过想要入门 Linux 的网络编程,套接字也是一个绕不开的重要知识点,正是有了套接字,Linux 系统才拥有了网络通信的能力.而且网络协议的最底层也是套接字 ...

  8. XmlNamespaceManager与XmlDocument.NameTable浅述

    XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); XmlNamespaceManager nsmgr=new XmlNamespaceMan ...

  9. B1061 判断题 (15 分)

    描述 判断题的评判很简单,本题就要求你写个简单的程序帮助老师判题并统计学生们判断题的得分. 输入格式: 输入在第一行给出两个不超过 100 的正整数 N 和 M,分别是学生人数和判断题数量.第二行给出 ...

  10. Serial-Studio 上位机编译全过程深度讲解,解决串口数据可视化工具

    Windows环境下编译Serial-Studio Serial-Studio是一个开源的串口数据可视化工具,广泛应用于物联网.嵌入式系统调试和数据分析等领域.从源代码编译Serial-Studio可 ...