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. 【SpringCloud】SpringCloud Alibaba Sentinel实现熔断与限流

    SpringCloud Alibaba Sentinel实现熔断与限流 限流与降级 限流 blockHandler 降级 fallback 降级需要运行时出现异常才会触发,而限流一旦触发,你连运行的机 ...

  2. 利用标准IO函数接口实现计算一个本地磁盘某个文件的大小,文件名通过命令行进行传递

    利用标准IO函数接口实现计算一个本地磁盘某个文件的大小,文件名通过命令行进行传递 方法一:使用ftell函数直接获取光标偏移量 相关标准库函数 SYNOPSIS #include <stdio. ...

  3. nodejs集群

    nodejs集群 单个 Node.js 实例运行在单个线程中. 为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务. 集群中的Master 现在让我们详细了解Master的职责 ...

  4. Assets, Resources and AssetBundles(五):AssetBundle usage patterns

    这是系列文章中的第五章,内容涉及"Unity5"中的资产.资源和资源管理. 本系列的前一章介绍了AssetBundles的基本原理,其中包括各种加载API的低级行为.本章讨论了在实 ...

  5. Python+Selenium+unittest实例

    代码如下: # coding=utf-8 import time import unittest from selenium import webdriver class BaiduSearch(un ...

  6. 🎀截图工具推荐-Snipaste

    简介 Snipaste 是一款非常强大且免费的截图和屏幕标记工具,由一位来自中国的开发者开发.它以其简洁的界面和丰富的功能而受到广泛好评. 官网 https://zh.snipaste.com/ Sn ...

  7. VBA_LoadPicture报错:子过程或子函数未定义

    需要增加如下引用: While in the VBE select Tools>References>find and check "OLE Automation" 参 ...

  8. Vue(七)——事件处理

    前情提要: v-on--监听DOM事件,在触发时运行js代码 在内联语句处理器中访问原始的 DOM 事件.可以用特殊变量 $event 把它传入方法 示例: <div id="exam ...

  9. sql注入与防止sql注入

    数据库中的数据 sql代码 package com.zjw.jdbc2; import java.sql.Connection; import java.sql.DriverManager; impo ...

  10. SQL 日常练习 (十四)

    最近的项目都比较忙, 没太有时间来做练习, 不过 sql 这块, 还是始终要保持良好的手感, 我已经渐渐感觉到, 随着写得越来越多, 当然不只是在这里, 更多是在工作中, 不过涉及信息安全不能共享. ...