很多小伙伴都知道Arraylist和Linkedlist相比,查询快,增删慢,这是为什么呢?它底层又是怎样的结构呢?请随我一起从源码分析(如有错误请大佬指出,本人是几个月小白)

目录

  • 简介
    • 特点
    • 继承关系
    • 成员变量
    • 构造方法三个
    • 常用的增删改查
      • add方法
        • ensureCapacityInternal
        • rangeCheckForAdd
      • get方法set方法
      • remove方法
    • 其他常用方法
    • 总结

简介

java容器集合可分为key值存储(Collection)和key-value存储(Map)两类,如下图继承链中ArrayList则是继承AbstractList类,实现了Collection接口

特点

对于ArrayList主要特点:
基于Object数组实现的List集合
不像数组一样长度固定,ArrayList可以实现动态扩容
有序,可重复
线程非安全,效率高
基于数组,查询快,但删除慢
元素可以为null

接下来源码分析(JDK1.8)

继承关系

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable

可见ArrayList支持泛型,且继承AbstractList类,实现了List,RandomAccess, Cloneable, java.io.Serializable接口,除开List,其他三个仅仅标识ArrayList可以具有随机访问,克隆,序列化功能

例如Cloneable仅仅是一个接口里面没有内容,仅为标识作用,其他两个一样

public interface Cloneable {}

成员变量

private static final long serialVersionUID = 8683452581122892189L;// 默认初始容量为10private static final int DEFAULT_CAPACITY = 10;// 初始一个空的Object数组private static final Object[] EMPTY_ELEMENTDATA = {};/*** (源码)Shared empty array instance used for default sized empty instances. We* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when* first element is added.* (有道翻译)用于默认大小的空实例的共享空数组实例。我们将其与空的ELEMENTDATA区分开来,* 以便知道添加第一个元素时应该膨胀多少。* 后面默认构造函数将用到DEFAULTCAPACITY_EMPTY_ELEMENTDATA为elementData初始化*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.* 底层数组* transient关键字:大致上是,在实现Serilizable接口后* 将不需要序列化的属性前添加关键字transient,* 序列化对象的时候,这个属性就不会序列化到指定的目的地中。*/transient Object[] elementData; // 非私有简化嵌套类访问//ArrayList的大小(包含的元素数量),默认为0。private int size;

构造方法三个

    /*** 构造一个指定初始容量的Arraylist*/public ArrayList(int initialCapacity) {//如果大于0则初始化elementData数组if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];//如果初始容量为0,则创建空数组} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {//小于0则抛出异常throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}/*** 构造一个初始容量为10的空列表。*/public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}/*** Constructs a list containing the elements of the specified* collection, in the order they are returned by the collection's* iterator.* (有道翻译)构造一个包含指定集合元素的列表,按照集合的迭代器返回元素的顺序。** @param c将其元素放置到列表中的集合* @throws 如果指定的集合为空,则抛出NullPointerException*/public ArrayList(Collection<? extends E> c) {//将c转换为数组elementData = c.toArray();if ((size = elementData.length) != 0) {// c.toArray might (incorrectly) not return Object[] (see 6260652)//c.toArray可能(不正确)不返回Object[](参见6260652)//如果不正确,所以下面有判断if (elementData.getClass() != Object[].class)//使用Arrays.copyOf将elementData拷贝成Object数组elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// 长度为0则替换为空数组this.elementData = EMPTY_ELEMENTDATA;}}

常用的增删改查

add方法

add方法主要有2个

    /*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return <tt>true</tt> (as specified by {@link Collection#add})*/public boolean add(E e) {//先调用ensureCapacityInternal判断是否需要扩容ensureCapacityInternal(size + 1);  // 增加 modCount!!//将元素e存入elementData数组并且size++elementData[size++] = e;return true;}/*** Inserts the specified element at the specified position in this* list. Shifts the element currently at that position (if any) and* any subsequent elements to the right (adds one to their indices).** @param index index at which the specified element is to be inserted* @param element element to be inserted* @throws IndexOutOfBoundsException {@inheritDoc}*/public void add(int index, E element) {//这里因为是在指定索引位置添加元素,则需要调用rangeCheckForAdd检查索引位置rangeCheckForAdd(index);//再确保容量足够ensureCapacityInternal(size + 1);  // Increments modCount!!//调优arraycopy方法,将index插入元素的位置的后面元素依次后移,所以插入元素是非常耗时System.arraycopy(elementData, index, elementData, index + 1,size - index);//将element元素放入index索引处elementData[index] = element;size++;}

ensureCapacityInternal

//上面两个都调用了ensureCapacityInternal和rangeCheckForAdd方法

    /*** Increases the capacity of this <tt>ArrayList</tt> instance, if* necessary, to ensure that it can hold at least the number of elements* specified by the minimum capacity argument.** @param   minCapacity   the desired minimum capacity*/public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)// any size if not default element table? 0// larger than default for default empty table. It's already// supposed to be at default size.: DEFAULT_CAPACITY;if (minCapacity > minExpand) {ensureExplicitCapacity(minCapacity);}}//参数为minCapability=size+1,private void ensureCapacityInternal(int minCapacity) {//判断elementData数组是否为空数组,空的话则返回默认为10的长度和size+1的最大值,//之前size初始化默认为0,如果这是第一次则扩容为10,因为10>1;if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}private void ensureExplicitCapacity(int minCapacity) {//modCount+1;modCount++;// 这里判断需不需要扩容,minCapacity 为10,而现在length为0,还没添加元素,if (minCapacity - elementData.length > 0)grow(minCapacity);}//要分配的数组的最大大小。一些虚拟机在数组中保留一些头字。
//尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制
//这里设置 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** Increases the capacity to ensure that it can hold at least the* number of elements specified by the minimum capacity argument.** @param minCapacity the desired minimum capacity*/private void grow(int minCapacity) {// 记载length int oldCapacity = elementData.length;//设置新容量为oldCapacity 的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);//下面就是判断扩容后的长度和之前的长度,这里<0意思第一次没有元素newCapacity 为0;//则将默认的10赋予新容量if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//如果大于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//则调用hugeCapacity方法赋予newCapacity 值              if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win://minCapacity通常接近于size,所以这是一个胜利//最后调用Arrays.copyOf得到新的容量为newCapacity的数组elementData = Arrays.copyOf(elementData, newCapacity);}private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();//返回如果大于最大允许的长度,则返回 Integer.MAX_VALUE 尽量将最大的容量返回//否则返回 MAX_ARRAY_SIZEreturn (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}/*** Returns the number of elements in this list.** @return the number of elements in this list*/

rangeCheckForAdd

//判断是否越界private void rangeCheckForAdd(int index) {if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}

get方法set方法

//这个比较简单,直接调用rangeCheck检查索引,然后返回public E get(int index) {rangeCheck(index);return elementData(index);}public E set(int index, E element) {//一样检查索引rangeCheck(index);//保存之前index上的元素E oldValue = elementData(index);//覆盖并将之前的元素返回elementData[index] = element;return oldValue;}

remove方法

  //首先检查索引,然后modcount++,这里modcount也许有人不是很了解,这里只大致说一下//modCount 顾名思义就是修改次数,在迭代器中有一个属性expectedModCount;//在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。//在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,不相等则代表其他线程对这容器进行了修改,这里还有个Fail-Fast 机制,没了解的同学建议了解一下public E remove(int index) {rangeCheck(index);modCount++;//记录删除的元素E oldValue = elementData(index);//记录将移动的元素个数int numMoved = size - index - 1;//依次向前移动,这里又可以证明Arraylist删除非常的麻烦if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);//最后一个元素只为空,这样该元素就没被引用,而数组元素是放值的堆中,会被GC管理回收elementData[--size] = null; // clear to let GC do its workreturn oldValue;}//通过传入一个元素,判断时候有则删除,且返回true,否则返回falsepublic boolean remove(Object o) {//o==null,循环遍历,有则调用fastRemoveif (o == null) {for (int index = 0; index < size; index++)if (elementData[index] == null) {fastRemove(index);return true;}} else {//思路同上for (int index = 0; index < size; index++)if (o.equals(elementData[index])) {fastRemove(index);return true;}}return false;}//私有移除方法,它跳过边界检查并且不返回被移除的值。private void fastRemove(int index) {modCount++;int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its work}//清空数组,全部只为空public void clear() {modCount++;// clear to let GC do its workfor (int i = 0; i < size; i++)elementData[i] = null;size = 0;}

其他常用方法

如下代码很好理解不再多说

public boolean isEmpty() {return size == 0;}public boolean contains(Object o) {return indexOf(o) >= 0;}public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i]==null)return i;} else {for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}return -1;}public int lastIndexOf(Object o) {if (o == null) {for (int i = size-1; i >= 0; i--)if (elementData[i]==null)return i;} else {for (int i = size-1; i >= 0; i--)if (o.equals(elementData[i]))return i;}return -1;}

总结

可以从上面的源码看出,ArrayList底层是一个Object类型的数组,可以存储基本类型和引用类型,元素可以为空,因为是基于数组,具有索引,所以查询效率高,但是面对增删改时候,会依次移动元素导致效率下降,浪费时间
为什么非线程安全:上面在做add增加的时候,会进行扩容,在最后的copyOf方法的时候会返回elementData对象,如果有多个线程则会返回多个elementData对象,这时候后来的线程返回的elementData对象将会把之前的线程elementData对象覆盖,这里想要线程安全可以使用下面三个类
java.util.Collections.SynchronizedList
vector
java.util.concurrent.CopyOnWriteArrayList

好了,以上是个人总结的部分源码,有兴趣的小伙伴建议还是打开电脑看看源码,谢谢观看。

从底层源码认知Arraylist相关推荐

  1. ArrayList底层源码分析

    声明:本文为作者原创,请勿装载,如过转载,请注明转载地址 文章目录 ArrayList底层源码分析 1. 继承Serializable接口 2. 继承Cloneable接口 2.1 浅拷贝 2.2 深 ...

  2. java 自定义arraylist_Java 中模仿源码自定义ArrayList

    Java 中模仿源码自定义ArrayList 最近看了下ArrayList的源码,抽空根据ArrayList的底层结构写了一个功能简单无泛型的自定义ArrayLsit,帮助自己更好理解ArrayLis ...

  3. jdk1.8.0_45源码解读——ArrayList的实现

    转载自  jdk1.8.0_45源码解读--ArrayList的实现 一.ArrayList概述 ArrayList是List接口的可变数组的实现.实现了所有可选列表操作,并允许包括 null 在内的 ...

  4. HashMap 底层源码细致分析

    JDK集合HashMap 底层源码细致分析 前言 提示:对于初始 HashMap 的小伙伴来说,不推荐直接硬啃,建议先看一下如下几个视频教程之后再回头好好理解.(一遍看不懂则反复看,一小块一小块的找对 ...

  5. Jaca集合(四)Vector集合底层源码分析

    Vector的基本介绍: (1)Vector类的定义说明:我们进入源码界面进行查看: public class Vector<E>extends AbstractList<E> ...

  6. Java集合框架底层源码

    集合底层源码 Collection List ArrayList public abstract class AbstractList<E>{//操作数protected transien ...

  7. hashmap允许null键和值吗_hashMap底层源码浅析

    来源:https://blog.csdn.net/qq_35824590/article/details/111769203 hashmap是我们经常使用的一个工具类.那么知道它的一些原理和特性吗? ...

  8. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(一)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  9. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

最新文章

  1. 理解神经网络,从简单的例子开始(2)使用python建立多层神经网络
  2. 怎么修改与服务器的操作系统,怎么修改与服务器的操作系统
  3. oracle sql练习_数据分析之学习SQL
  4. 【专访】PP租车张丙军:“打虎亲兄弟“,好团队才能书写互联网传奇
  5. Android WebView与JS交互入门
  6. COM.MYSQL.JDBC.DRIVER 和 COM.MYSQL.CJ.JDBC.DRIVER的区别
  7. 随想录(easyx中的键盘输入和鼠标消息)
  8. 计算机科学常见工具书清单、项目开发清单
  9. Linux block multi-queue (blk-mq) 机制
  10. html5 图片羽化,html5+webgl仿ps羽化笔刷液态动画特效
  11. 随处可见抢眼美女 TechED盛宴人潮汹涌
  12. CCNA-思科网络基础(IP地址 MAC地址 DNS DHCP PING )
  13. 推流(RTSP/RTMP)
  14. R语言错误 --> Error in plot.new() : figure margins too large
  15. 时间触发嵌入式系统设计模式 读书笔记
  16. Materia Camera Mesh
  17. caffe源码 layer分析
  18. 条码 vs RFID: 条码应用的优势
  19. java网络传输压缩数据_Java数据压缩与传输实例Java源码
  20. 腾讯招聘日――招聘新体验

热门文章

  1. vvv在线文档导出工具_墙裂推荐一个好用的在线文档编辑工具
  2. 1-乙基-3-甲基咪唑醋酸盐([EMIM][Ac]);甲基三辛基醋酸铵[N(1,8,8,8)][Ac]齐岳离子液体
  3. PCF8563模块不走时(海振电子PCF8653模块)
  4. PCF8563使用心得
  5. 如何关闭win10自带杀毒?
  6. 医疗器械行业迎来黄金十年,集团采购系统助力企业把握机遇,实现高质量发展
  7. Vue el-input 使用伪元素插入间隔符
  8. 敏捷开发“松结对编程”实践之三:共同估算篇(大型研发团队,学习型团队,139团队,师徒制度,敏捷设计,估算扑克,扑克牌估算) .
  9. java自动填充_java 如何自动填充网页上的用户名和密码?
  10. 前端开发:Vue报错Avoid mutating a prop directly since the value will be…的解决方法