JDK1.8源码阅读(五):ArrayList

一、初识ArrayList

顾名思义,ArrayList的结构实际就是一个数组。所以它的特性很明显,插入一个元素的时候,是耗时是一个常量时间O(1),在插入n个元素的时候,需要的时间就是O(n)。

ArrayList类图设计:
在这里插入图片描述

  • ArrayList实现了RandomAccess接口:意味着其支持快速(通常是固定时间)随机访问。
  • ArrayList实现了Cloneable接口,意味着它能被克隆。
  • ArrayList实现了java.io.Serializable接口,意味着 它支持序列化。

二、源码解析

1、成员变量

// 初始化默认容量
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;
// 数组能申请的最大数量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2、构造函数

// 无参构造函数
public ArrayList() {
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定初始化容量的构造函数
public ArrayList(int initialCapacity) {
  // 当initialCapacity > 0 时,初始化对应大小的数组
  if (initialCapacity > 0) {
    this.elementData = new Object[initialCapacity];
  // 为0时,用指向EMPTY_ELEMENTDATA
  } else if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
  } else {
    throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
  }
}
// 参数一个集合的构造函数
public ArrayList(Collection<? extends E> c) {
  elementData = c.toArray();
  if ((size = elementData.length) != 0) {
    // c.toArray不返回Object[]的时候,则进行数组拷贝
    if (elementData.getClass() != Object[].class)
      elementData = Arrays.copyOf(elementData, size, Object[].class);
  } else {
    // elementData.length = 0 ,指向EMPTY_ELEMENTDATA
    this.elementData = EMPTY_ELEMENTDATA;
  }
}

3、get方法

public E get(int index) {
  // 检查index范围是否正确
  rangeCheck(index);
  return elementData(index);
}
// 检查index范围是否正确
private void rangeCheck(int index) {
  // 如果index 大于存储的个数,则抛出异常
  if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
// 从数组里取出元素
E elementData(int index) {
  return (E) elementData[index];
}

4、 add(E e)方法

public boolean add(E e) {
  // 对构造方法初始化的数组进行处理
  ensureCapacityInternal(size + 1);
  // 赋值,然后指针走到下一个空位
  elementData[size++] = e;
  return true;
}

// 初始化数组的大小
private void ensureCapacityInternal(int minCapacity) {
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 取minCapacity和DEFAULT_CAPACITY中较大的那个
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  }
  // 检查是否要扩容
  ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
  // 修改次数的计数器
  modCount++;
  // 如果需要的空间大小 > 当前数组的长度,则进行扩容
  if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}

private void grow(int minCapacity) {
  // 记录旧的length
  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);
  // 旧数组拷贝到新的大小数组
  elementData = Arrays.copyOf(elementData, newCapacity);
}

// 最大的容量
private static int hugeCapacity(int minCapacity) {
  // 大小溢出
  if (minCapacity < 0)
    throw new OutOfMemoryError();
  // 需要的最小容量 > 数组最大的长度,则取Integer的最大值,否则取数组最大长度
  return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
  1. 对于刚初始化的数组,要初始化它的大小
  2. 判断数组大小是否足够,如果不够大,扩容1.5倍,对于扩容要判断是否到达数组的最大数量

5、remove(int index)

 public E remove(int index) {
  rangeCheck(index);
  // 修改计数器
  modCount++;
  // 记录旧值,返回
  E oldValue = elementData(index);
  // 计算要往前移动的元素个数
  int numMoved = size - index - 1;
  // 个数大于0,进行拷贝,从index+1开始拷贝,拷贝起始位置是index
  if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);
  // 设置为null,以便GC
  elementData[--size] = null;
  return oldValue;
}

删除指定位置的元素,其后面的元素需要往前移。

6、remove(Object o)方法

public boolean remove(Object o) {
  // 判断o为null,loop遍历找到为null的元素
  if (o == null) {
    for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
        fastRemove(index);
        return true;
      }
  // 不为null
  } 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;
}

7、set(int index, E element)方法

public E set(int index, E element) {
  rangeCheck(index);
  // 记录旧的值
  E oldValue = elementData(index);
  // 在原位置设置新的值
  elementData[index] = element;
  return oldValue;
}

设置index位置的元素值为element,返回该位置的原来的值

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

public boolean addAll(Collection<? extends E> c) {
  Object[] a = c.toArray();
  int numNew = a.length;
  // 对于新的最小长度进行判断处理
  ensureCapacityInternal(size + numNew);
  // 拷贝
  System.arraycopy(a, 0, elementData, size, numNew);
  // 将size增加numNew个
  size += numNew;
  return numNew != 0;
}

三、总结

ArrayList在随机访问的时候,数组的结构导致访问效率比较高,但是在指定位置插入,以及删除的时候,需要移动大量的元素,导致效率低下,在使用的时候要根据场景特点来选择,另外注意循环访问的方式选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值