Java集合之ArrayList源码解析

31 篇文章 0 订阅
16 篇文章 2 订阅

目录

1.ArrayList

1.1整体结构

1.2初始化源码解析

1.3新增和扩容源码解析

1.4删除源码分析

2.迭代器

2.1总体结构

2.2三个方法源码解析


1.ArrayList


1.1整体结构

  • 初始化大小,默认是10;第一次add的时候扩容的值
  • size表示当前数组大小
  • modCount表示当前版本号

1.2初始化源码解析

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//无参数直接初始化,数组大小为空
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//指定初始数据初始化
public ArrayList(Collection<? extends E> c) {
    //elementData 是保存数组的容器,默认为 null
    //object类型的数组是可以接收其他类型的数组的!!!
    elementData = c.toArray();
    //如果给定的集合(c)数据有值
    if ((size = elementData.length) != 0) {
        // 这一步是基本是不会进入的,因为toArray方法永远返回object类型数组
        // 但是,如果使用Arrays.asList返回的List进行初始化返回的是自己内部实现的ArrayList,
        // 它的的toArray方法使用的是clone一个真实类型E的数组,object数组是可以接收的,
        // 但是必须进行强制转换为object类型的数组    
        if (elementData.getClass() != Object[].class) {
            elementData = Arrays.copyOf(elementData, size, Object[].class);
        }
    } else {
        // 给定集合(c)无值,则默认空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

//指定大小初始化
public ArrayList(int initialCapacity) {
    //首先判断值的有效性
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    //初始化指定大小
    this.elementData = new Object[initialCapacity];
}
//Arrays的静态方法
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

//Arrays的静态内部类
private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
{
    // 直接是基本类型的数组.....
    private final E[] a;
    ......

    // object类型数组是可以接收的
    public Object[] toArray() {
        return a.clone();
    }

    .....
}

初始化主要分为:

指定数据初始化,使用toArray转换为数组并进行赋值,进行判断传入的集合长度是否为0,在判断是否需要转换为object数组类型。

ps:object类型的数组(elementData )真实类型可能不是object的,因为它是可以接收其他类型的数组的,所以需要判断是否进行强制类型转换!

无参数初始化,直接初始化为空的object数组

指定大小初始化,先判断传入的大小值是否合格,然后直接申请指定大小的object数组

ps:Arrays.asList(array)方法返回的不是ArrayList而是Arrays自己实现的一个静态内部类,不能增加删除数据。

1.3新增和扩容源码解析

public boolean add(E e) {
  //确保数组大小是否足够,不够则执行扩容,size 为当前数组的大小
  ensureCapacityInternal(size + 1);  // Increments modCount!!
  //直接赋值,线程不安全的
  elementData[size++] = e;
  return true;
}

private void ensureCapacityInternal(int minCapacity) {
  //有初始化数据或者初始化大小,不走 if 逻辑
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    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) {
  int oldCapacity = elementData.length;
  // 扩容1.5倍
  int newCapacity = oldCapacity + (oldCapacity >> 1);

  // 如果扩容后的值 < 我们的期望值,那么就等于我们的期望值
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

  // 如果扩容后的值 > 所能分配的数组的最大值,那么就用 Integer 的最大值
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
 
  // 通过Arrays的复制进行扩容
  elementData = Arrays.copyOf(elementData, newCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

//最大可分配和Int最大值就差8,没甚么意义,等价于返回Int最大值
private static int hugeCapacity(int minCapacity) {
    // 判断溢出,如果溢出就会重新循环到负数
    if (minCapacity < 0) 
            throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

新增时,首先判断是否需要扩容,先判断是否是没有初始化大小和数据(没有初始化就是10和期待大小的最大值),

在进行数组版本号++,然后判断当前长度是否小于期待的值,

进行扩容1.5倍,如果扩容之后的值小于所期待的值,就设置为期待值,如果扩容之后的值大于最大可分配数,就扩容到INT的最大值即可

最后,使用Arrays进行实际的拷贝扩容

1.4删除源码分析

删除元素的方式有很多:索引删除,值删除,批量删除......,大致思路都差不多,现在来说明下根据值来进行删除,

public boolean remove(Object o) {
  // 如果要删除的值是 null,找到第一个值是 null 的删除
  if (o == null) {
    for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
        fastRemove(index);
        return true;
      }
  } else {
    // 如果要删除的值不为 null,找到第一个和要删除的值相等的删除
    for (int index = 0; index < size; index++)
      // 这里是根据  equals 来判断值相等的,相等后再根据索引位置进行删除
      if (o.equals(elementData[index])) {
        fastRemove(index);
        return true;
      }
  }
  return false;
}

private void fastRemove(int index) {
  // 记录数组的结构要发生变动了
  modCount++;
  // numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去
  int numMoved = size - index - 1;
  if (numMoved > 0)
    // 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
    System.arraycopy(elementData, index+1, elementData, index, numMoved);
  //数组最后一个位置赋值 null,帮助 GC
  elementData[--size] = null;
}

根据值来进行删除:首先判断该值是否为null,再根据循环确定出删除元素的第一个位置

要修改数组需要进行版本号++,然后计算要移动元素的个数,最后进行实际的拷贝


 

2.迭代器


2.1总体结构

  • int cursor;// 表示下一个元素的索引位置,默认从 0 开始。
  • int lastRet = -1; // add时:表示当前元素的索引位置,默认从-1开始。
  • int expectedModCount = modCount(数组实际版本号);// expectedModCount 表示迭代过程中,期望的版本号;

一般来说,有三个方法,hasNext判断还有没有值可以迭代,next迭代值的索引,remove删除当前迭代值

2.2三个方法源码解析

//hasNext
public boolean hasNext() {
 //cursor 表示下一个元素的位置,size 表示实际大小,
 //如果两者相等,说明已经没有元素可以迭代了,如果不等,说明还可以迭代
  return cursor != size;
}

//add
public E next() {
  //迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常
  checkForComodification();
  //本次迭代过程中,元素的索引位置
  int i = cursor;
  //size一定大于等于object数组的length属性,size表示元素的实际个数,length表示数组长度
  //就是判断是否可以迭代,否则抛出异常
  if (i >= size)
    throw new NoSuchElementException();
  Object[] elementData = ArrayList.this.elementData;
  // 为下一次迭代做准备
  cursor = i + 1;
  // 返回元素值,并进行强制类型转换
  return (E) elementData[lastRet = i]; //index返回值为i
}

// 版本号检验
final void checkForComodification() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

//remove
public void remove() {
  // 防止重复删除,限定一个当前index只能删除一次
  if (lastRet < 0)
    throw new IllegalStateException();
  //判断版本号有无被修改
  checkForComodification();

  try {
    //进行实际的删除
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    // -1 表示元素已经被删除,这里也防止重复删除
    lastRet = -1;
    // 更新版本号
    expectedModCount = modCount;
  } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
  }
}

hasNext,直接使用下一个元素的索引与size元素个数进行比较

next,先判断版本号,在判断当前索引是否等于size,防止索引越界(判断是否可以迭代),

在将当前索引增加为下一次迭代做准备,最后返回当前索引位置的元素,并设置当前索引元素的索引位置

remove,先判断当前索引位置是否小于0,小于的话就说明已经删除过了无法再次删除,在判断版本号,

在进行实际的删除,在把下一个元素的指针向前移动指向当前索引位置,并且设置当前索引位置为-1,最后要更新版本号

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值