Java集合系列-ArrayList内部探究

1,352 阅读6分钟

前言

在Java中集合使用的频率还是非常高、导致面试的常见问题、网上大部分的都有各种大佬做讲解的博客、本篇文章是根据网上资料、做的一个小总结。

JAVA版本1.8

1610451412355.png

图上紫色的是基本的常用的集合实现类、我们从Collection顶级接口来看起

Collection接口

int size(); // 获取集合长度

boolean isEmpty(); // 判断集合是否为空

boolean contains(Object o);  // 判断集合里元素是否包含这个元素

Iterator<E> iterator();  // 迭代器

Object[] toArray();  // 集合转为数组

<T> T[] toArray(T[] a);  // 将集合中元素返回为指定元素类型的数组

boolean add(E e); // 添加一个元素到集合中

boolean remove(Object o); // 将集合中应该元素移除

boolean containsAll(Collection<?> c);  // 检查集合中是否包含另一个集合中的所有元素

boolean addAll(Collection<? extends E> c); // 将另一个集合中的元素添加到当前集合中

boolean removeAll(Collection<?> c); // 移除另一个集合中包含的元素

boolean retainAll(Collection<?> c); // 保留另一个集合包含的元素

void clear(); // 移除集合中所有元素

boolean equals(Object o); // 比较集合是否指向同一地址

int hashCode(); // 返回当前集合的hash值

Collection接口就一些待实现的方法、在List继承Collection是List接口也同样有一些接口定义的方法。 这定义待实现的方法就好比如一个模板、就好比如你要去继承Collection接口、顺带将这些方法显示在自定义的接口中、你就好理解、看的比较清晰明了。

定义一个接口

public interface aList<E> extends Collection<E> {
   
}

// List接口
public interface List<E> extends Collection<E> {
// List的接口就写了这样一个
    int size();
}

在定义一个实现类

public class tList<E> implements aList<E> {
    @Override
    public int size() {
        return 0;
    }
    // 省略很多方法实现...
}

测试

public static void main(String[] args) {
    aList<Integer> a = new tList<Integer>();
    System.out.println(a.size());
}

// 结果:0

这里解释的就是为什么Collection接口中定义的方法待实现、为什么List子类要在写一份、作用就为了确定当前接口中有哪些需要待实现的类、而不是一直点父类点到头看父类接口定义了什么待实现的方法、(个人理解)

ArrayList集合说明

在上面说了ArrayList是List接口实现类由此我们可以使用多态的形式来创建一个集合

List<Integer> list = new ArrayList<>();
// 创建一个集合、形式为多态、父类 = new 子类实现();

什么是多态:编译看左、运行看右。

属性

// 默认初始容量
private static final int DEFAULT_CAPACITY = 10;

// 都是默认空数组、只是用的地方不一样
private static final Object[] EMPTY_ELEMENTDATA = {};  // 做为空数组对elementData赋值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 做为对初始扩容条件的判断

// 存放数据分最终地方
transient Object[] elementData; 

// 数据大小
private int size;

构造器

// ArrayList源码构造
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);
  }
}

public ArrayList() { // 创建一个默认的集合
  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

public ArrayList(Collection<? extends E> c) { // 简单来说就是将一个集合中的数据初始到当前集合中
  elementData = c.toArray();
  if ((size = elementData.length) != 0) {
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
      elementData = Arrays.copyOf(elementData, size, Object[].class);
  } else {
    // replace with empty array.
    this.elementData = EMPTY_ELEMENTDATA;
  }
}

从一个类属性和构造器可以看出、集合内部是由数组实现的、还发现的DEFAULT_CAPACITY集合初始容量10

对集合CRUD简单说明

public boolean add(E e) {
    // 验证是否需要扩容操作
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 在对应的下标下添加值
    elementData[size++] = e;
    return true;
}

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

public E set(int index, E element) {
    // 校验下标是否越界
    rangeCheck(index);
    // 获取对应下标的值
    E oldValue = elementData(index);
    // 将值重新赋值
    elementData[index] = element;
    // 返回之前元素
    return oldValue;
}

public E get(int index) {
     // 校验下标是否越界
    rangeCheck(index);
    // 获取对应下标的值
    return elementData(index);
}

CURD小结 这种其实不是ArrayList集合的主要、这种简单的可以自行前去查看源码中的内容、主要的还是扩容机制。

ArrayList集合扩容机制

扩容的话还是要看添加方法、从属性中可以看到有一个DEFAULT_CAPACITY属性值10、下面来看他的作用

public boolean add(E e) {
    // 验证是否需要扩容操作
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 在对应的下标下添加值
    elementData[size++] = e;
    return true;
}

它刚开始进入ensureCapacityInternal(size + 1)size是个全部变量、他是标记ArrayList的大小(它包含的元素数)、此时没有元素就是ensureCapacityInternal(0 + 1)

//1、进入这个方法
private void ensureCapacityInternal(int minCapacity) {
  //2、先调用calculateCapacity(elementData, minCapacity)、这个方法就是检查第一次添加数据、并返回默认的容器大小(就是10)
  // 4、就是执行这个方法ensureExplicitCapacity(10) // 为什么参数是10、就是因为这个方法calculateCapacity(elementData, minCapacity)做了操作。
  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//3、数组容量计算
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  // 这个能处理就是第一次添加数据时为真
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 然后这个成立、这个方法是取参数1和参数2、两个数之间的最大值
    // DEFAULT_CAPACITY : 默认为10
    // minCapacity : 第一次添加数据为1
    // 所以10 和 1 、10大、最后将10返回出去
    return Math.max(DEFAULT_CAPACITY, minCapacity);
  }
  return minCapacity;
}

//5、确保显式容量
private void ensureExplicitCapacity(int minCapacity) {
  modCount++;

   // 6、做判断、
  // 第一次添加数据时 为 10-0 > 0 :第一次可以成立
  // 第二次添加数据时 为 2-10 > 0 :第二次可以不成立
  if (minCapacity - elementData.length > 0)
    //7、执行下面方法、这个方法的作用才是正真的实施扩容并确定首次扩容ArrayList容器大小的方法
    grow(minCapacity);//扩容
}

图解 微信图片_20210114145319.png

grow() 方法

//minCapacity 所需的最小容量
private void grow(int minCapacity) {
  // overflow-conscious code
  // 获取当前数组容器容量大小
  int oldCapacity = elementData.length;
  // 扩容。新的容量=当前容量+(当前容量/2)、即将当前容量增加一半(当前容量增加1.5倍)
  int newCapacity = oldCapacity + (oldCapacity >> 1);
  // 判断如果进行扩容后还是小于最小容量
  if (newCapacity - minCapacity < 0)
    // 所需的最小容量 赋值给newCapacity新容量
    newCapacity = minCapacity;
  // 如果扩容后的容量大于临界值,则进行大容量分配
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    // hugeCapacity(minCapacity) 做的事情就是minCapacity小于0抛出内存溢出异常
    // 或者返回一个更大的容量给新容量赋值
    newCapacity = hugeCapacity(minCapacity);
  // minCapacity is usually close to size, so this is a win:
  //新的容量大小已经确定好了,就copy数组,改变容量大小。
  //copyof(原数组,新的数组长度)
  elementData = Arrays.copyOf(elementData, newCapacity);
}
//进行大容量分配
private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) // overflow  //如果minCapacity<0,抛出内存溢出异常
    throw new OutOfMemoryError();
  // 如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE
  return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
  MAX_ARRAY_SIZE;
}

1610610392.jpg

 // 细心的朋友会发现这个地方有一个减8的操作了吧
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

image.png 我已经帮你翻译好了、哈哈哈哈。

整体的流程图

1610870085430.png

小结

  • ArrayList底层结构为数组结构、而自动扩展元素空间的大小的具体实现在每次添加数据时中进行判断扩充、关键方法为gorw()方法
  • 默认容量大小为10、默认扩充倍数为:(当前容量增加1.5倍)
  • ArrayList可以存放null值、非线程安全的集合 、可以存放重复元素

有问题可以一起讨论、虚心学习、后续会总结一些其他实现类的实现原理

附赠鸡汤

解答方式有很多种、正确的答案不是唯一成长的标准、而是过程。解答方式有很多种、正确的答案不是唯一成长的标准、而是过程。