Java源码系统之ArrayList

Java源码系列之ArrayList

一、介绍

java.util.ArrayList<E> 是非常重要的一个类,在代码中广泛使用,E表示泛型,ArrayList是一个泛型类。

ArrayList相当于C++ 的vector,用于存储对象。与数组不同,数组一旦创建,长度固定,但是ArrayList的长度是动态的,不受限制,可以存储任意多的对象,但是只能存储对象,不能存储原生数据类型例如int。 它的继承类图关系如下:

从图上可以看出,ArrayList继承了AbstractList,实现了List、Cloneable、Serializable、RandomAccess接口。

 

二、源码分析

基本属性

 /**
  * 默认初始容量,大小为10.
  */
  private static final int DEFAULT_CAPACITY = 10;
 ​
  /**
  * 空的数组,传入容量为0时使用.
  */
  private static final Object[] EMPTY_ELEMENTDATA = {};
 ​
  /**
  * 空的数组,传入容量时使用,当容器添加第一个元素时,会进行扩容,具体见add(E e)方法.
  */
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 ​
  /**
  * 真正存储元素的数组
  */
  transient Object[] elementData; // non-private to simplify nested class access
 
  /**
  * 要分配的数组的最大值
  */
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  • DEFAULT_CAPACITY :默认容量为10,也就是通过new ArrayList()创建时的默认容量。

  • EMPTY_ELEMENTDATA :空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。

  • elementData:真正存放元素的地方,使用transient是为了不序列化这个字段。

  • size:真正存储元素的个数,而不是elementData数组的长度。

  • MAX_ARRAY_SIZE:数组长度的最大值是Integer.MAX_VALUE - 8

    数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。

    如果数组长度过大,可能出现的两种错误

    OutOfMemoryError: Java heap space 堆区内存不足(这个可以通过设置JVM参数 -Xmx 来指定)。

    OutOfMemoryError: Requested array size exceeds VM limit 超过了JVM虚拟机的最大限制,我的window64就是 Integer.MAX_VALUE-1 .

构造方法之ArrayList(int initialCapacity)

     public ArrayList(int initialCapacity) {
  if (initialCapacity > 0) {
  //如果传入的初始容量值大于0,则初始化一个初始容量大小的数组,用来存储元素
  this.elementData = new Object[initialCapacity];
  } else if (initialCapacity == 0) {
  //如果等于0,则把空数组EMPTY_ELEMENTDATA赋给elementData
  this.elementData = EMPTY_ELEMENTDATA;
  } else {
  //如果小于0,则抛出异常
  throw new IllegalArgumentException("Illegal Capacity: "+
  initialCapacity);
  }
  }

构造方法之ArrayList()

     public ArrayList() {
  //使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,在添加第一个元素的时候会扩容
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  }

构造方法之ArrayList(Collection<? extends E> c)

     public ArrayList(Collection<? extends E> c) {
  //传入的集合转为数组,并赋值给elementData
  elementData = c.toArray();
  //判断传入的集合的大小是否为0
  if ((size = elementData.length) != 0) {
  //判断集合toArray()返回的结果是否是Object[].class,如果不是,则重新拷贝成Object[].class类型
  if (elementData.getClass() != Object[].class)
  elementData = Arrays.copyOf(elementData, size, Object[].class);
  } else {
  //如果传入的集合为0,则初始化为空数组EMPTY_ELEMENTDATA
  this.elementData = EMPTY_ELEMENTDATA;
  }
  }

方法之add(E e)

     /**
  * 添加元素
  */
  public boolean add(E e) {
  //检查是否需要扩容
  ensureCapacityInternal(size + 1); // Increments modCount!!
  //添加元素到最后一位
  elementData[size++] = e;
  return true;
  }
 ​
  private void ensureCapacityInternal(int minCapacity) {
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  //如果elementData为空数组,则取默认容量和传入的所需最小容量(minCapacity)之中大的值
  //并把该值重新赋值给minCapacity
  minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  }
 ​
  ensureExplicitCapacity(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)
  //如果新容量要大于所允许的最大长度,则会使用最大容量为Integer的最大值,
  //否则使用MAX_ARRAY_SIZE
  newCapacity = hugeCapacity(minCapacity);
  //以新容量拷贝出来一个新数组
  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;
  }

方法之add(int index, E element)

     /**
  * 添加元素到指定位置
  */
  public void add(int index, E element) {
  //检查是否越界
  rangeCheckForAdd(index);
  //检查是否需要扩容
  ensureCapacityInternal(size + 1); // Increments modCount!!
  //将下标为index及之后的元素向后移一位,这样index就空出来了
  System.arraycopy(elementData, index, elementData, index + 1,
  size - index);
  //将元素插入到index的位置
  elementData[index] = element;
  //大小加1
  size++;
  }
 ​
  private void rangeCheckForAdd(int index) {
  if (index > size || index < 0)
  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  }

方法之addAll(Collection<? extends E> c)

     /**
  * 求两个集合的并集
  */
  public boolean addAll(Collection<? extends E> c) {
  //将集合c转为数组
  Object[] a = c.toArray();
  //集合a的大小
  int numNew = a.length;
  //扩容所需的容量=已有数组的大小+c集合的大小
  ensureCapacityInternal(size + numNew); // Increments modCount
  //将集合c的元素,拷贝到数组的最后
  System.arraycopy(a, 0, elementData, size, numNew);
  //size加上c集合的大小
  size += numNew;
  //如果集合c不为空,返回true,否则返回false
  return numNew != 0;
  }

方法之get(int index)

     /**
  * 获取指定位置的元素
  */
  public E get(int index) {
  //检查是否越界
  rangeCheck(index);
  //返回数组index的元素
  return elementData(index);
  }
 ​
  private void rangeCheck(int index) {
  if (index >= size)
  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  }
 
  E elementData(int index) {
  return (E) elementData[index];
  }

注:获取指定索引位置的元素,时间复杂度为O(1)。

方法之remove(int index)

     /**
  * 删除指定位置元素
  */
  public E remove(int index) {
  //检查是否越界
  rangeCheck(index);
 ​
  modCount++;
  //获取index位置的元素
  E oldValue = elementData(index);
 ​
  int numMoved = size - index - 1;
  //如果index不是最后一位,则将index之后的元素往前挪一位
  if (numMoved > 0)
  System.arraycopy(elementData, index+1, elementData, index,numMoved);
  //讲最后一个元素删除,置空,帮助GC回收
  elementData[--size] = null; // clear to let GC do its work
  //返回旧值,即删除的index对应的值
  return oldValue;
  }

注:删除指定索引位置的元素,时间复杂度为O(n);而且从源码上可以看出,删除元素后ArrayList并没有缩容。

方法之remove(Object o)

     /**
  * 删除指定元素值的元素
  */
  public boolean remove(Object o) {
  if (o == null) {
  //遍历整个数组,找到元素第一次出现的位置,并将其快速删除
  for (int index = 0; index < size; index++)
  //如果要删除的元素为null,则使用==进行比较,满足条件则进行快速删除,并返回true
  if (elementData[index] == null) {
  fastRemove(index);
  return true;
  }
  } else {
  //遍历整个数组,找到元素第一次出现的位置,并将其快速删除
  for (int index = 0; index < size; index++)
  //如果要删除的元素不为null,则使用equals进行比较,满足条件则进行快速删除,并返回true
  if (o.equals(elementData[index])) {
  fastRemove(index);
  return true;
  }
  }
  return false;
  }
 ​
  /*
  * Private remove method that skips bounds checking and does not
  * return the value removed.
  */
  private void fastRemove(int index) {
  //和remove(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
  }

方法之retainAll(Collection<?> c)

     /**
  * 求两个集合的交集
  */
  public boolean retainAll(Collection<?> c) {
  //校验集合c是否为null,为null则抛出NullPointerException
  Objects.requireNonNull(c);
  //调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素
  return batchRemove(c, true);
  }
 ​
  /**
  * 批量删除
  * @param c 集合
  * @param complement 为true,表示删除不包含在c中的元素,否则表示删除包含c中的元素
  * @return
  */
  private boolean batchRemove(Collection<?> c, boolean complement) {
  final Object[] elementData = this.elementData;
  //使用读写两个指针同时遍历数组
  //读指针每次自增1,写指针放入元素的时候才加1
  //这样不需要额外的空间,只需要在原有的数组上操作就可以了
  int r = 0, w = 0;
  boolean modified = false;
  try {
  //遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
  for (; r < size; r++)
  if (c.contains(elementData[r]) == complement)
  elementData[w++] = elementData[r];
  } finally {
  // Preserve behavioral compatibility with AbstractCollection,
  // even if c.contains() throws.
  //正常来说r最后是等于size的,除非c.contains()抛出了异常
  if (r != size) {
  //如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
  System.arraycopy(elementData, r,
  elementData, w,
  size - r);
  w += size - r;
  }
  if (w != size) {
  //将写指针之后的元素置为空,帮助GC回收
  for (int i = w; i < size; i++)
  elementData[i] = null;
  modCount += size - w;
  //新的大小等于写指针的位置,因为每写一次写指针就加1,所以新大小正好等于写指针的位置
  size = w;
  //有修改,设置modified=true
  modified = true;
  }
  }
  return modified;
  }

方法之removeAll(Collection<?> c)

     /**
  * 删除包含c集合的元素,即求差集
  */
  public boolean removeAll(Collection<?> c) {
  //检查集合c不能为null
  Objects.requireNonNull(c);
  //批量删除,complement为false,表示删除包含在c中的元素
  return batchRemove(c, false);
  }

elementData之序列化

 private void writeObject(java.io.ObjectOutputStream s)
  throws java.io.IOException{
  //防止序列化期间有修改
  int expectedModCount = modCount;
  //写出非transient非static属性(会写出size属性)
  s.defaultWriteObject();
 ​
  //写出元素个数
  s.writeInt(size);
 ​
  //依次写出元素
  for (int i=0; i<size; i++) {
  s.writeObject(elementData[i]);
  }
 ​
  //如果有修改,抛出异常
  if (modCount != expectedModCount) {
  throw new ConcurrentModificationException();
  }
 }
 ​
 private void readObject(java.io.ObjectInputStream s)
  throws java.io.IOException, ClassNotFoundException {
  //声明为空数组
  elementData = EMPTY_ELEMENTDATA;
 ​
  //读入非transient非static属性(会读取size属性)
  s.defaultReadObject();
 ​
  //读入元素个数,没什么用,只是因为写出的时候写了size属性,读的时候也要按顺序来读
  s.readInt();
 ​
  if (size > 0) {
  //计算容量
  int capacity = calculateCapacity(elementData, size);
  SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
  //检查是否需要扩容
  ensureCapacityInternal(size);
 ​
  Object[] a = elementData;
  //依次读取元素到数组中
  for (int i=0; i<size; i++) {
  a[i] = s.readObject();
  }
  }
 }

注:

查看writeObject()方法可知,先调用s.defaultWriteObject()方法,再把size写入到流中,再把元素一个一个的写入到流中。

一般地,只要实现了Serializable接口即可自动序列化,writeObject()和readObject()是为了自己控制序列化的方式,这两个方法必须声明为private,在java.io.ObjectStreamClass#getPrivateMethod()方法中通过反射获取到writeObject()这个方法。

在ArrayList的writeObject()方法中先调用了s.defaultWriteObject()方法,这个方法是写入非static非transient的属性,在ArrayList中也就是size属性。同样地,在readObject()方法中先调用了s.defaultReadObject()方法解析出了size属性。

elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。

 

总结

  • ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;

  • ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1);

  • ArrayList添加元素到尾部极快,平均时间复杂度为O(1);

  • ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n);

  • ArrayList从尾部删除元素极快,时间复杂度为O(1);

  • ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n);

  • ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;

  • ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;

  • ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;

转载于:https://www.cnblogs.com/wanmeilingdu/p/11195608.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值