ArrayList的类定义

public class ArrayList extends AbstractList        implements List, RandomAccess, Cloneable, java.io.Serializable{

实现 List 接口,继承了 AbstractList 抽象类,这个是很好理解的,ArrayList作为List集合类家族的一部分,可以更好的将公用方法抽象给上层

实现Cloneable和Serializable接口 ,可以进行克隆和序列化操作

实现RandomAccess接口,这个接口会相对陌生点,点进去官方给的解释是,RandomAccess是一个标记接口,是一个空接口,仅起到标记的作用(类似Serializable接口)

public interface RandomAccess {}

List实现这个接口表明这个类能实现 快速随机访问。该接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问或顺序访问列表时提供良好的性能

而后文档里给了说明,如果实现了RandomAccess接口,对List做查询操作时使用for循环的方式,否则使用迭代器的方式。这样做的原因做了RandomAccess接口标记的LIst实现类底层数据结构使用for循环效率更高(比如ArrayList),没有RandomAccess接口标记的使用迭代器效率更高(比如LinkedList,下文可以看到LinkedList没有实现RandomAccess接口)

举个简单的小例子,比如Collections类里的二分查找方法,就是用是否标记了RandomAccess接口来区分用哪种方法实现的:

public static  int binarySearch(List extends Comparable super T>> list, T key) {  if (list instanceof RandomAccess || list.size()    return Collections.indexedBinarySearch(list, key);  // for循环方法  else    return Collections.iteratorBinarySearch(list, key); // Iterator循环方法}

ArrayList构造函数

public ArrayList() {  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ArrayList实现了三种构造函数用于不同情形下的对象创建

a. 无参构造了一个空数组,在首次添加元素的时候才给数据设置大小的构造函数

b. 给定初始容量的构造函数,通过参数 initialCapacity 可设置List数组的初始大小,这样做的好处是当 ArrayList 新增元素时,如果所存储的元素已经超过其已有大小,它会计算元素大小后再进行动态扩容,数组的扩容会导致整个数组进行一次内存复制。因此,我们在初始化 ArrayList 时,可以通过第一个构造函数合理指定数组初始大小,这样有助于减少数组的扩容次数,从而提高系统性能

public ArrayList(int initialCapacity) {  if (initialCapacity > 0) {     // initialCapacity大于0,数组的大小为initialCapacity值    this.elementData = new Object[initialCapacity];  } else if (initialCapacity == 0) {    // initialCapacity等于0,生成默认空数组EMPTY_ELEMENTDATA    this.elementData = EMPTY_ELEMENTDATA;  } else {    // 其他情况,报非法参数异常-》数组的大小必须大于等于0    throw new IllegalArgumentException("Illegal Capacity: "+                                       initialCapacity);  }}

c.传入Collection对象转化成ArrayList,将 Collection 转化为数组并赋值给 elementData,把 elementData 中元素的个数赋值给 size。如果 size 不为零,则判断 elementData 的 class 类型是否为 Object[],不是的话则做一次转换。如果 size 为零,则把 EMPTY_ELEMENTDATA 赋值给 elementData,相当于new ArrayList(0)

public ArrayList(Collection extends E> c) {  Object[] a = c.toArray();  if ((size = a.length) != 0) {    if (c.getClass() == ArrayList.class) {      elementData = a;    } else {      elementData = Arrays.copyOf(a, size, Object[].class);    }  } else {    // replace with empty array.    elementData = EMPTY_ELEMENTDATA;  }}

ArrayList属性

transient Object[] elementData; // non-private to simplify nested class accessprivate static final int DEFAULT_CAPACITY = 10;private int size;

elementData :底层数组,用来存储数据

DEFAULT_CAPACITY :数组的默认初始化容量 10

size :当前的数组大小,用来表示当前数组包含了多少个元素

ArrayList新增元素

/** * 直接将元素添加到数组尾部 */public boolean add(E e) {  // 1.判断当前数组容量是否够用,不够的话进行数组扩容操作  ensureCapacityInternal(size + 1);  // Increments modCount!!  // 2.将元素添加到数组尾部,size加1  elementData[size++] = e;  return true;}/** * 将元素添加到指定位置 */public void add(int index, E element) {  // 1.判断指定位置是否在数组包含范围内  rangeCheckForAdd(index);  // 2.判断当前数组容量是否够用,不够的话进行数组扩容操作  ensureCapacityInternal(size + 1);  // Increments modCount!!  // 3.将指定位置开始的元素向后挪动一位  System.arraycopy(elementData, index, elementData, index + 1,                   size - index);  // 4.将元素添加到指定位置上  elementData[index] = element;  size++;}

ArrayList提供了两种添加元素的方法,第一种:直接将元素添加到数组尾部;第二种:将元素添加到指定位置

从代码里可以看出,两种添加方式都执行了 ensureCapacityInternal 方法,判断数组的容量情况,具体看下这个方法是如何实现的呢:

判断当前数组是否是空数组EMPTY_ELEMENTDATA,如果是的话,判断默认值(10)和传入的容量(size+1)谁大,就返回哪个,如果不是空数组的话,直接返回(size+1),这一步是对初始化为空数组的情况进行特殊处理

判断传入容量(size+1)是否大于当前的数组大小,如果是的话进行扩容操作(grow)

扩容操作是将容量扩充到原始容量的1.5倍大小,如果还是不够,就将容量给定为当前传入的容量(size+1)

这里需要判断下是否有内存溢出的问题,当容量达到允许的最大值(MAX_ARRAY_SIZE)的时候,将数组容量给定为int最大值

扩容后需要将原数组重新分配到新的内存地址中

/** * 计算数组容量,从传入的容量和默认容量(10)里面去较大的 */private static int calculateCapacity(Object[] elementData, int minCapacity) {  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {    return Math.max(DEFAULT_CAPACITY, minCapacity);  }  return minCapacity;}/** * 计算数组容量,从传入的容量和默认容量(10)里面去较大的 */private void ensureCapacityInternal(int minCapacity) {  ensureExplicitCapacity(calculateCapacity(elementData, 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;  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) {  if (minCapacity < 0) // overflow    throw new OutOfMemoryError();  return (minCapacity > MAX_ARRAY_SIZE) ?    Integer.MAX_VALUE :  MAX_ARRAY_SIZE;}/** * 数据允许的最大容量 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

从代码里可以看到,添加元素到任意位置,会导致在该位置后的所有元素都需要重新排列,而将元素添加到数组的末尾,在没有发生扩容的前提下,是不会有元素复制排序过程的。所以直接将元素添加的数组的末尾效率会更高

ArrayList查找元素

public E get(int index) {  // 检查index是否越界,  rangeCheck(index);  // 返回数组对应下标元素值  return elementData(index);}private void rangeCheck(int index) {  if (index >= size)    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}@SuppressWarnings("unchecked")E elementData(int index) {  return (E) elementData[index];}

ArrayList在数据查找上的效率很高,只需要O(1)的时间复杂度就能获取数据,传入需要元素的下标index,返回数据中的对应元素即可

ArrayList删除元素

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; // clear to let GC do its work  return oldValue;}

ArrayList删除元素的代码逻辑和添加元素到任意位置的方法类似,在每一次有效的删除元素操作之后,都要进行数组的重组,并且删除的元素位置越靠前,数组重组的开销就越大

  1. 判断index是否合法(有没有下标越界)

  2. 获取要删除的元素

  3. 将要删除数据下标往后的元素向前移动一位

  4. 将最后一位置零

  5. 返回被删除的元素

今天的分享就到这里,一起学技术,一起在生活中探险~欢迎关注【小肖爱吃肉】

arraylist是如何扩容的?_ArrayList的源码分析相关推荐

  1. 【Java源码分析】Java8的ArrayList源码分析

    Java8的ArrayList源码分析 源码分析 ArrayList类的定义 字段属性 构造函数 trimToSize()函数 Capacity容量相关的函数,比如扩容 List大小和是否为空 con ...

  2. Java学习集合源码分析

    集合源码分析 1.集合存在的原因 可以用数组来表示集合,那为什么还需要集合? 1)数组的缺陷 ​ 在创建数组时,必须指定长度,一旦指定便不能改变 数组保存必须是同一个类型的数据 数组的增加和删除不方便 ...

  3. HashMap与ConcurrentHashMap万字源码分析

    HashMap与ConcurrentHashMap`源码解析 JDK版本:1.7 & 1.8 ​ 开发中常见的数据结构有三种: 1.数组结构:存储区间连续.内存占用严重.空间复杂度大 优点:因 ...

  4. Java源码详解五:ArrayList源码分析--openjdk java 11源码

    文章目录 注释 类的继承与实现 构造函数 add操作 扩容函数 remove函数 subList函数 总结 本系列是Java详解,专栏地址:Java源码分析 ArrayList 官方文档:ArrayL ...

  5. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  6. 集合之ArrayList(含JDK1.8源码分析)

    一.ArrayList的数据结构 ArrayList底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据.我们对ArrayList类的实例的所有的操作(增删改查等),其底层都 ...

  7. List接口的常用方法以及ArrayList/LinkedList源码分析

    1.List接口的常用方法 ArrayList list = new ArrayList();list.add(123);list.add(456);list.add("AA"); ...

  8. 面试必会之ArrayList源码分析手写ArrayList

    作者:Java知音-微笑面对生活 简介 ArrayList是我们开发中非常常用的数据存储容器之一,其底层是数组实现的,我们可以在集合中存储任意类型的数据,ArrayList是线程不安全的,非常适合用于 ...

  9. ArrayList相关方法介绍及源码分析

    目录 ArrayList简介: ArrayList 相关方法介绍 代码表示 相关方法源码分析 ArrayList简介: java.util.ArrayList 是我们最常用的一个类,ArrayList ...

最新文章

  1. java 判断当前时间是否为节假日_最近公司招人,研发组商量了下,暂时定下这么多java面试题!...
  2. Bagging和Boosting 概念及区别
  3. 大咖分享 | 产品经理如何成长进阶?
  4. 如何访问ASP.Net网站bin目录内的文件
  5. Hollowjars,部署扫描程序以及Wildfly群体为何很棒
  6. python程序题求roc-auc是一种常用的模型评价指标_Keras 利用sklearn的ROC-AUC建立评价函数详解...
  7. python xposed_xposed + python 怎么爬取微信朋友圈的数据
  8. pytorch构造可迭代的Dataset——IterableDataset(pytorch Data学习二)
  9. C++11 中的内联函数、auto关键字、for循环及空指针
  10. AWS服务器可以设置虚拟ip吗,《AWS云计算实战》3.使用虚拟服务器:EC2
  11. java日期格式正则表达式_Java-日期 正则表达式
  12. php 将url转成伪静态,php兑现url伪静态化,全过程详解
  13. 华为p8刷linux系统,华为手机助手ROM一键刷机
  14. python读取xml文件并修改_如何在Python中解析和修改XML?
  15. matlab2014如何获得hostid,hostid.c/获取主机标识
  16. 视频教程-计算机二级(VB)-计算机等级考试
  17. 150个节点的本地机房建设解决方案
  18. 细说final的的四种用法-----修饰类,修饰方法,常量,修饰参数 及内部类与final
  19. Windows下使用taskkill 命令结束进程
  20. 六款好用的视频编辑软件推荐

热门文章

  1. 详解/etc/profile、/etc/bash.bahsrc、~/.profile、~/.bashrc的用途
  2. 几个常用的文本处理shell 命令:find、grep、xargs、sort、uniq、tr、cut、paste、wc、sed、awk
  3. C++、C#写的WebService相互调用
  4. VC++下命名管道编程的原理及实现
  5. python怎么新开一行_极简主义的践行者:一行python可以怎么玩?
  6. Linux下1号进程的前世(kernel_init)今生(init进程)----Linux进程的管理与调度
  7. Ubuntu9.10下安装Thinkpad T400 R29无线网卡
  8. Microsoft Teams快速上手系列-06邀请组织外部用户加入团队
  9. 用关系型NoSQL回到未来
  10. ASP之防止外部数据提交的脚本