ArrayList底层代码逻辑分析

一、数据结构与成员变量

ArrayList内部主要使用一个Object类型的数组elementData来存储元素。此外,它还有两个重要的成员变量:size表示当前列表中实际存储的元素个数;modCount用于记录对列表结构修改的次数,主要在迭代器中用于检测并发修改。

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 transient int modCount = 0;

二、构造方法

默认构造方法:当使用默认构造方法创建ArrayList时,内部数组被初始化为一个空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA。这意味着在首次添加元素时,会将内部数组的容量设置为默认容量 10。

   public ArrayList() {
       this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }

指定初始容量的构造方法:如果传入一个正整数作为初始容量参数,ArrayList会创建一个指定大小的数组来存储元素。如果传入的初始容量为 0,则内部数组被初始化为EMPTY_ELEMENTDATA。如果传入的初始容量小于 0,则会抛出IllegalArgumentException异常。

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

三、添加元素方法

  1. add(E e)方法:
    • 这个方法用于在列表末尾添加一个元素。首先,它会调用ensureCapacityInternal(size + 1)来确保内部数组有足够的空间容纳新元素。如果当前数组已满,就会触发扩容操作。
    • 然后,将新元素赋值给内部数组的下一个空位(当前size位置),并将size加一。
   public boolean add(E e) {
       ensureCapacityInternal(size + 1);  
       elementData[size++] = e;
       return true;
   }
  1. add(int index, E element)方法:
    • 此方法用于在指定位置插入一个元素。首先,通过rangeCheckForAdd(index)检查指定的索引是否合法。如果索引小于 0 或大于当前列表的大小,就会抛出IndexOutOfBoundsException异常。
    • 接着,调用ensureCapacityInternal(size + 1)确保有足够的空间。如果需要扩容,会先进行扩容操作。
    • 然后,使用System.arraycopy方法将指定位置及之后的元素向后移动一位,为新元素腾出空间。最后,将新元素插入到指定位置,并将size加一。
   public void add(int index, E element) {
       rangeCheckForAdd(index);
       ensureCapacityInternal(size + 1);
       System.arraycopy(elementData, index, elementData, index + 1,
                        size - index);
       elementData[index] = element;
       size++;
   }

四、扩容机制

  1. ensureCapacityInternal(int minCapacity)方法:
    • 这个方法首先计算内部数组需要的最小容量。如果当前内部数组为空(即elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA),则将最小容量设置为默认容量(10)和传入的最小容量中的较大值。
    • 然后,调用ensureExplicitCapacity(minCapacity)方法进一步处理扩容需求。
   private void ensureCapacityInternal(int minCapacity) {
       ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
   }
  1. calculateCapacity(Object[] elementData, int minCapacity)方法:
    • 如果内部数组为空,根据传入的最小容量和默认容量(10)确定新的最小容量。如果内部数组不为空,则直接返回传入的最小容量。
   private static int calculateCapacity(Object[] elementData, int minCapacity) {
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           return Math.max(DEFAULT_CAPACITY, minCapacity);
       }
       return minCapacity;
   }
  1. ensureExplicitCapacity(int minCapacity)方法:
    • 增加modCount,表示对列表进行了一次结构修改。
    • 如果所需的最小容量大于当前数组的长度,则调用grow(minCapacity)方法进行扩容。
   private void ensureExplicitCapacity(int minCapacity) {
       modCount++;
       if (minCapacity - elementData.length > 0)
           grow(minCapacity);
   }
  1. grow(int minCapacity)方法:
    • 首先获取当前数组的旧容量oldCapacity
    • 计算新容量newCapacity,通常为旧容量的 1.5 倍(旧容量加上旧容量右移一位的值)。
    • 如果新容量仍然小于所需最小容量,则将新容量设置为所需最小容量。
    • 如果新容量超过了最大数组大小(Integer.MAX_VALUE - 8),则根据所需最小容量调用hugeCapacity(minCapacity)方法来确定最终的新容量。
    • 最后,使用Arrays.copyOf方法将原数组中的元素复制到新的更大的数组中,并更新内部数组的引用。
   private void grow(int minCapacity) {
       int oldCapacity = elementData.length;
       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);
   }
  1. hugeCapacity(int minCapacity)方法:
    • 如果所需最小容量大于最大数组大小,则抛出OutOfMemoryError异常。否则,返回Integer.MAX_VALUE和所需最小容量中的较小值与最大数组大小中的较小值。
   private static int hugeCapacity(int minCapacity) {
       if (minCapacity < 0) // overflow
           throw new OutOfMemoryError();
       return (minCapacity > MAX_ARRAY_SIZE)?
           Integer.MAX_VALUE :
           MAX_ARRAY_SIZE;
   }

五、获取元素方法

  1. get(int index)方法:
    • 这个方法用于获取指定索引处的元素。首先,通过rangeCheck(index)检查索引是否合法。如果索引小于 0 或大于等于当前列表的大小,就会抛出IndexOutOfBoundsException异常。
    • 然后,直接返回内部数组中对应索引处的元素。
   public E get(int index) {
       rangeCheck(index);
       return elementData(index);
   }

六、删除元素方法

  1. remove(int index)方法:
    • 此方法用于删除指定索引处的元素。首先,通过rangeCheck(index)检查索引是否合法。
    • 然后,记录要删除的元素的值oldValue
    • 计算需要移动的元素个数numMoved,即列表中指定索引之后的元素个数。
    • 如果有元素需要移动(即numMoved大于 0),则使用System.arraycopy方法将指定索引之后的元素向前移动一位。
    • 最后,将列表的大小减一,并将最后一个位置的元素设置为null,以便垃圾回收器可以回收该元素。同时,返回被删除的元素。
   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; 
       return oldValue;
   }
  1. remove(Object o)方法:
    • 这个方法用于删除指定的元素。如果元素为null,则遍历列表找到第一个为null的元素并删除;如果元素不为null,则遍历列表找到第一个与给定元素相等(通过equals方法判断)的元素并删除。
    • 如果没有找到要删除的元素,则返回false
   public boolean remove(Object o) {
       if (o == null) {
           for (int index = 0; index < size; index++)
               if (elementData[index] == null) {
                   fastRemove(index);
                   return true;
               }
       } else {
           for (int index = 0; index < size; index++)
               if (o.equals(elementData[index])) {
                   fastRemove(index);
                   return true;
               }
       }
       return false;
   }
  • fastRemove(int index)方法是一个内部辅助方法,用于快速删除指定索引处的元素,其实现与remove(int index)方法类似,但不返回被删除的元素。
   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;
   }

七、遍历方式

  1. 使用迭代器遍历:
    • ArrayList实现了Iterable接口,因此可以使用迭代器进行遍历。迭代器提供了一种统一的方式来遍历集合,并且可以在遍历过程中安全地删除元素。
    • 通过调用list.iterator()可以获取一个Iterator对象,然后使用hasNext()next()方法来遍历列表中的元素。
   Iterator<E> iterator = list.iterator();
   while (iterator.hasNext()) {
       E element = iterator.next();
       // 对元素进行操作
   }
  1. 使用增强型for循环遍历:
    • 增强型for循环(也称为“foreach”循环)提供了一种简洁的方式来遍历集合。在内部,它使用迭代器来遍历集合中的元素。
    • 语法为for (E element : list),其中element是每次迭代的当前元素,list是要遍历的ArrayList对象。
   for (E element : list) {
       // 对元素进行操作
   }

总之,ArrayList底层通过数组实现,具有高效的随机访问性能,但在插入和删除元素时可能需要移动大量元素,效率相对较低。其扩容机制确保了可以动态地存储任意数量的元素,同时提供了多种添加、删除、获取和遍历元素的方法,方便开发者在不同场景下使用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值