通过底层代码可以学习到很多东西:

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

由此可见,ArrayList继承自AbastractList,以及实现了以上四个接口;

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{..}  ==> AbstractCollection<E> implements Collection<E>{..}

List<E> extends Collection<E>{..};

由上引申最后的根源都是Collection;

private static final int DEFAULT_CAPACITY = 10;这是默认的初始化容量;

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};以上两个是可以类内部引用的空数组,后者是充当类的默认空值

transient Object[] elementData;  被transient 修饰的变量不会被序列化;

此处注意:该元素不被序列化但是当序列化时元素还是有的;玄机在于ArrayList中的两个方法:

ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。至于为什么不直接用elementData来序列化,原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间;

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);
  }

}

这个方法可以定义初始化容量大小的ArrayList;

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

由此可知:当new一个ArrayList对象时,他是一个默认的空数组;

public void trimToSize() {
  modCount++;
  if (size < elementData.length) {
    elementData = (size == 0)
    ? EMPTY_ELEMENTDATA
    : Arrays.copyOf(elementData, size);
  }
}

此方法可以用来去除动态增长的多余容量,节省空间;ArrayList所说没有用的值并不是null,而是ArrayList每次增长会预申请多一点空间,1.5倍+1,而不是两倍
这样就会出现当size() = 1000的时候,ArrayList已经申请了1200空间的情况;
trimToSize 的作用只是去掉预留元素位置,就是删除多余的200,改为只申请1000,内存紧张的时候会用到;(可自行通过代码调用debug查看元素即知)

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);
  }
}

我们在使用Arraylist时,经常要对它进行初始化工作,在使用add()方法增加新的元素时,如果要增加的数据量很大,应该使用ensureCapacity()方法,该方法的作用是预先设置Arraylist的大小,这样可以大大提高初始化速度;总而言之,记住这个函数可以对低层数组扩容就行了,在适当的时机,好好利用这个函数,将会使我们写出来的程序性能得到提升;

至于原因是因为,如果添加元素时第一次没有一次性扩到想要的最大容量的话,它就会在添加元素的过程中,一点一点的进行扩容,要知道对数组扩容是要进行数组拷贝的,这就会浪费大量的时间。如果已经预知容器可能会装多少元素,最好显示的调用ensureCapacity这个方法一次性扩容到位。

public E get(int index) {
  rangeCheck(index);

  return elementData(index);
}

@SuppressWarnings("unchecked")
  E elementData(int index) {
  return (E) elementData[index];
}

由此可见get(i)实质是通过数组的index方式来获取元素;

当ArrayList执行add()方法添加元素时,每次都会去增加modCount;至于这个modCount的用处:

由于ArrayList是非线程安全的,而modCount记录了ArrayList结构性变化的次数;在使用迭代器遍历的时候,用来检查列表中的元素是否发生结构性变化(列表元素数量发生改变)了,主要在多线程环境下需要使用,防止一个线程正在迭代遍历,另一个线程修改了这个列表的结构;

还有:

由此可见,执行add()方法当容量超出了原最大容量,会继续扩容哦(在grow()方法中);

remove()方法,当下标越界时在rangeCheck(index)方法中会抛IndexOutOfBoundsException异常;

至于System.arraycopy(elementData, index+1, elementData, index,numMoved); 解析如下:

第一个参数:elementData是原有的字节数组。

第二个参数:index+1是从原有的字节数组中开始截取的位置,以0开始。

第三个参数:elementData是目标字节数组。

第四个参数:index是目标数据开始拷贝的位置,位置以0开始, 如果再次拷贝需要把(index参数+numMoved参数)为起始位置。

第五个参数:是原有字节数组(index+1)参数开始截取的字节长度 相当于substring(index+1,index+1+numMoved)==>即substring(index+1,size);

放上原方法更清晰些:

参数:
src:源数组;	srcPos:源数组要复制的起始位置;
dest:目的数组;	destPos:目的数组放置的起始位置;	length:复制的长度。

简单描述就是从src的srcPos位置拷贝length长度的数据到dest的destPos位置,如果src和dest是同一个对象的话,则相当于先将数据拷贝到一个临时的数组,然后再覆盖数组中destPos开始的一段数据。

elementData[--size] = null;当将变量设为null时,GC机制会自动垃圾处理回收呦~;

clear()清空方法,即执行此方法后ArrayList()是一个空数组了;

elementData[i] = null;此方法用于GC机制来回收缓存垃圾;

ensureCapacityInternal(size + numNew);该方法用于计算新增后的容量;

System.arraycopy(a, 0, elementData, size, numNew);将要添加的集合(即转换后数组)从原ArrayList(数组)后面添加;

此方法实质执行了arraycopy()方法两次:

System.arraycopy(elementData, index, elementData, index + numNew,numMoved); 将index后的元素往新添加集合后的后面坐标开始加入,即往后移了,将原来空间让给新添加的集合元素;

System.arraycopy(a, 0, elementData, index, numNew);将新添加的集合元素放入;

可以将集合元素剖析为三部分:a(从开始到fromIndex),b(要删除的部分即fromIndex到toIndex),c(从toIndex到最后)

System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);

此方法就是将c部分的元素放到从b部分起点元素那边开始放;

最后将后面多余元素设为null,通过GC机制回收处理掉;

  • removeAll(Collection<?> c):将c中所有存在的元素从本ArrayList中移除。调用batchRemove方法,将所有需要保存的元素存在elementData前部分,w的最后值为要保存元素的数量。r!=size是为了确保没有出错,若r!=size,则前面抛出了异常,接着将从r开始的size-r个元素复制到ArrayList后面。如果w==size,则表示没有删除,否则将w之后的元素清空。
  • retainAll(Collection<?> c):将ArrayList中所有在c中存在的元素保留。与上述方法类似,调用batchRemove方法,将所有需要保存的元素保存在elementData前部分,后序也如同上述过程。

    for (; r < size; r++)
      if (c.contains(elementData[r]) == complement)
    elementData[w++] = elementData[r];

    解析:若保留,将相同的元素移动到前段。若删除,将不同的元素移动到前段;然后:

  if (w != size) {
    // clear to let GC do its work
    for (int i = w; i < size; i++)
    elementData[i] = null;
    modCount += size - w;
    size = w;
    modified = true;
  }

将后端的元素设为null,最后通过GC机制处理掉;

推荐相关资源阅读:https://juejin.im/post/58edf7cbb123db43cc36f47f

ArrayList底层代码解析笔记的更多相关文章

  1. LinkedList底层代码解析笔记

    LinkedList是属于Sequence List,故遍历是用迭代器更快; LinkedList继承自AbstractSequenceList.实现了List及Deque接口.其实AbstractS ...

  2. 笔记-爬虫-js代码解析

    笔记-爬虫-js代码解析 1.      js代码解析 1.1.    前言 在爬取网站时经常会有js生成关键信息,而且js代码是混淆过的. 以瓜子二手车为例,直接请求https://www.guaz ...

  3. java笔记 -- java简单结构代码解析及注释

    结构代码解析 public class FirstSample { public static void main(String[] args) { System.out.println(2.0-1. ...

  4. ArrayList和LinkedList的底层代码实现思想

    ArrayList是Java众多集合类中的一个,实现List接口,List的父接口是Collection.ArrayList底层的数据结构是线性表中的顺序表,底层是一个长度可以动态增长的数组.数组有很 ...

  5. 【论文笔记】AutoML for MCA on Mobile Devices——论文解读与代码解析

    理论部分 方法介绍 本节将详细介绍AMC的算法流程.AMC旨在自动地找出每层的冗余参数. AMC训练一个强化学习的策略,对每个卷积层会给出其action(即压缩率),然后根据压缩率进行裁枝.裁枝后,A ...

  6. 顺序线性表 ---- ArrayList 源码解析及实现原理分析

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...

  7. ArrayList源码解析

    ArrayList简介 ArrayList定义 1 public class ArrayList<E> extends AbstractList<E> implements L ...

  8. 面试必备:ArrayList源码解析(JDK8)

    面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来 ...

  9. ArrayList源码解析,老哥,来一起复习一哈?

    前言 JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK14已经出来,但是JDK8我还不会,我... 类图 实现了RandomAccess接口,可以随机访问 实现了Cloneable接口,可以 ...

随机推荐

  1. Python数字类型及数学运算

    1.数字类型分为int.float和complex,complex暂时用不到,int和float的相关运算和类型转换如下: >>> 59+12 71 >>> 59- ...

  2. log4j日志格式化

    Apache log4j 提供了各种布局对象,每一个对象都可以根据各种布局格式记录数据.另外,也可以创建一个布局对象格式化测井数据中的特定应用的方法. 所有的布局对象 - Appender对象收到 L ...

  3. 第四周总结和实验二Java简单类与对象

    实验目的 掌握类的定义,熟悉属性.构造函数.方法的使用,掌握用类作为类型声明变量和方法返回值: 理解类和对象的区别,掌握构造函数的使用,熟悉通过对象名引用实列的方法和属性: 理解static修饰对类. ...

  4. centos7.3 安装gitlab

    系统自带ruby版本太低,需要手动编译2.4版本

  5. docker-compose的scale的用法

    用到的三个文件 docker-compose version: " services: redis: image: redis web: build: context: . dockerfi ...

  6. Javascript中的相等比较

    在比较相等或不相等之前,会对操作数进行类型转换,然后比较相等性 在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则: 1.如果由一个操作数是布尔值,则在比较相等性之前先将其转换为数值:2.如果 ...

  7. suffixes - 列出文件后缀。

    DESCRIPTION [描述] 文件后缀与文件名之间以点(.)间隔,通常包括一个或多个字母. 我们用文件后缀来描述文件的内容.很多标准的实用程序,如编译器,以后缀来识别文件类型. make(1) 就 ...

  8. 初撩Django-RESTful-rest_framework视图函数

    rest_framework中的视图函数: View函数 APIView函数 APIView的子类 mixins函数 viewsets函数 1.View函数 2.APIView函数 3.APIView ...

  9. Java数据库事务四大特性以及隔离级别

    四大特性ACID 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚.失败回滚的操作事务,将不能对数据库有任何影响 一致性(Consistency) 一致性是指事 ...

  10. Java高并发网络编程(五)Netty应用

    推送系统 一.系统设计 二.拆包和粘包 粘包.拆包表现形式 现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下: 第一种情 ...