List接口的主要实现类ArrayList,是线程不安全的,执行效率高;底层基于Object[] elementData 实现,是一个动态数组,它的容量能动态增加和减少。可以通过元素下标访问对象,使用于快速检索场景时使用。

  基于JDK1.8,通过ArrayList几个常用的方法,分析ArrayList原理。

属性及继承关系

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
}

   ArrayList继承AbstractList类,并实现List接口;

   RandomAccess:是一个标记接口,继承了RandomAccess接口的集合支持随机快速访问

   Cloneable:继承Cloneable接口,重写clone()方法,能实现拷贝功能

Serializable:支持序列化,可存储和传输

空构造函数及带参构造函数

ArrayList()

public ArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

  当我们new ArrayList()时会初始化elementData属性为空数组{},此时底层的数组并没有被实例化,所以操作ArrayList其实就是围绕elementData这个数组而进行。

ArrayList(int initialCapacity)

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 ;可以从源码看出如果 initialCapacity 大于 0 ,会实例化一个指定长度的Object数组赋值给elementData ;如果 initialCapacity 等于 0 则依然赋值为空;否则抛出异常信息。

  通过以上两个构造函数,可以很明确ArrayList底层其实是一个Object[] 数组,而调用ArrayList提供的方法,其实就是操作数组。

add(E e)

public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}

  add(E e) 方法的作用是添加一个元素到列表末尾,方法第一行调用ensureCapacityInternal(size + 1); 代码如下:

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}

  判断elementData为空数组时则返回DEFAULT_CAPACITY, minCapacity这两个中的最大值,接着调用ensureExplicitCapacity(minCapacity);代码如下:

private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

  可以看出这里其实就是判断是否需要进行扩容,条件是当我们所需要的数组长度减去数组的长度大于0时,会调用grow(minCapacity)进行扩容;代码如下:

private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}

  声明一个newCapacity属性,值为原数组长度的1.5倍且进行判断,如果扩容后的长度减去我们需要的数组长度小于0则使用扩容后的长度,如果扩容后的长度减去MAX_ARRAY_SIZE大于0则使用Integer的最大值(Integer.MAX_VALUE) ,这里的MAX_ARRAY_SIZE 实则是Integer.MAX_VALUE - 8,接下来就是拷贝一个新的数组

  通过add(E e)方法的源代码,又可以很明确知道当我们在对ArrayList集合添加元素的时候,其实会对底层elementData数组的长度进行判断并动态调整且产生一个新的数组回来   

remove(int index)

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);
elementData[--size] = null;
return oldValue;
}

  remove(int index)方法的作用是按照索引位置删除并返回元素;第一行代码rangeCheck(index);检查索引是否越界,代码如下:

private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

  E oldValue = elementData(index);获取指定下标的数据,计算出需要移动的位置,调用native方法进行数组移动,改变size长度,且将--size位置置空等待GC回收,最终返回之前的值

get(int index)

public E get(int index) {
rangeCheck(index);
return elementData(index);
}

  get(int index)方法的作用是获取指定下标的元素;第一步检查索引是否合法,根据下标获取elementData数组中的数据并返回

set(int index, E element)

public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

  set(int index, E element)方法的作用是设置指定下标的元素值;第一步检查索引是否合法,然后获取之前的值,并将之前下标的值更改为当前数据,返回老数据

透过源码分析ArrayList运作原理的更多相关文章

  1. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

  2. 从定时器的选型,到透过源码看XXL-Job(下)

    透过源码看xxl-job (注:本文基于xxl-job最新版v2.0.2, quartz版本为 v2.3.1. 以下提到的调度中心均指xxl-job-admin项目) 上回说到,xxl-job是一个中 ...

  3. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  4. Kafka源码分析及图解原理之Producer端

    一.前言 任何消息队列都是万变不离其宗都是3部分,消息生产者(Producer).消息消费者(Consumer)和服务载体(在Kafka中用Broker指代).那么本篇主要讲解Producer端,会有 ...

  5. Guava 源码分析(Cache 原理 对象引用、事件回调)

    前言 在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理. 文末提到了回收机制.移除时间通知等内容,许多朋友也挺感兴趣,这次就这两个内容再来分析分析. 在开 ...

  6. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  7. php中foreach源码分析(编译原理)

    php中foreach源码分析(编译原理) 一.总结 编译原理(lex and yacc)的知识 二.php中foreach源码分析 foreach是PHP中很常用的一个用作数组循环的控制语句.因为它 ...

  8. 通过源码分析Java开源任务调度框架Quartz的主要流程

    通过源码分析Java开源任务调度框架Quartz的主要流程 从使用效果.调用链路跟踪.E-R图.循环调度逻辑几个方面分析Quartz. github项目地址: https://github.com/t ...

  9. Robotium源码分析之运行原理

    从上一章<Robotium源码分析之Instrumentation进阶>中我们了解到了Robotium所基于的Instrumentation的一些进阶基础,比如它注入事件的原理等,但Rob ...

随机推荐

  1. NLP(二十五)实现ALBERT+Bi-LSTM+CRF模型

      在文章NLP(二十四)利用ALBERT实现命名实体识别中,笔者介绍了ALBERT+Bi-LSTM模型在命名实体识别方面的应用.   在本文中,笔者将介绍如何实现ALBERT+Bi-LSTM+CRF ...

  2. 什么是phpMyAdmin

    phpMyAdmin 是一个以PHP为基础,以Web-Base方式架构在网站主机上的MySQL的数据库管理工具,让管理者可用Web接口管理MySQL数据库.借由此Web接口可以成为一个简易方式输入繁杂 ...

  3. day1 执行用例

    故事1 今天的工作,是执行已写好的"回馈"模块用例 每个子模块的用例都有优先级之分 肯定要先进行通过性测试,验证正常流 因此,我优先执行各个子模块优先级最高的那条用例,进行通过性测 ...

  4. Java中将文件夹复制到另一个文件夹

    文件夹的拷贝*** public static void copyDir(String sourcePath, String newPath) { File start = new File(sour ...

  5. linux中的源码安装

    前两天自己在笔记本上装了CentOs版本的虚拟机,接着要装Python3,是源码安装的挺费劲,个人总结了一些源码安装的经验,今天在这里给大家分享一下. 1. 首先准备环境,安装必要的编译工具gcc g ...

  6. Python模块二

    os模块是与操作系统交互的一个接口​ <em>#和文件夹相关 os.makedirs('dirname1/dirname2')    可生成多层递归目录 os.removedirs('di ...

  7. XiaoQi.Study 项目(三)

    一.配置跨域 1.首先注册跨域要求 ,(可访问的IP.端口) //注册跨域 services.AddCors(options => { options.AddPolicy("XiaoQ ...

  8. 关于WPF System.windows.Media.FontFamily 的类型初始值设定项引发异常问题解决方法

    造成原因:此问题的根本原因是.NET Framework January 2018 Rollup(KB4055002)与已安装的.NET Framework 4.7.1产品版本之间的MSI安装交互.R ...

  9. 国际惯例,Hello World。

    c语言: #include<stdio.h> int main() { printf("Hello World!\n"); ; } C++: #include<i ...

  10. POJ1144 tarjan+网络中割点与割边的数量

    题目链接:http://poj.org/problem?id=1144 割点与割边的数量我们可以通过tarjan的思想从一个点开始对其余点进行访问.访问的顺序构成一棵dfs树,其中根节点到任何一个结点 ...