找回密码
 立即注册
首页 业界区 业界 4.Java SDK源码分析系列笔记-ArrayList

4.Java SDK源码分析系列笔记-ArrayList

韩素欣 2025-6-29 09:20:09
目录

  • 1. 是什么
  • 2. 如何使用
  • 3. 原理分析

    • 3.1. uml
    • 3.2. 构造方法
    • 3.3. add方法

      • 3.3.1. 确保容量足够容纳新的元素
      • 3.3.2. 把元素放入数组最后一个位置

    • 3.4. remove方法【按下标删除元素】

      • 3.4.1. 把数组index位置之后的数据往前挪
      • 3.4.2. 更新size【数组不缩容】

    • 3.5. remove方法【按元素内容删除】

      • 3.5.1. 首先找到要删除的元素的下标
      • 3.5.2. 把数组index位置之后的数据往前挪
      • 3.5.3. 更新size【数组不缩容】


  • 4. ConcurrentModificationException

    • 4.1. 原因分析
    • 4.2. 解决


1. 是什么

底层由数组实现的,可扩容的顺序表
有序、可以重复
2. 如何使用
  1. public class ArrayListTest
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         ArrayList<String> list = new ArrayList<>();
  6.         list.add("1");
  7.         list.add("2");
  8.         System.out.println(list);
  9.         
  10.         list.remove(0);
  11.         list.remove("2");
  12.     }
  13. }
复制代码
3. 原理分析

3.1. uml

1.png

可以看出ArrayList是个List、可克隆、可序列化、可以使用下标访问
3.2. 构造方法

使用object数组,并且初始化长度为0
  1. public class ArrayList<E> extends AbstractList<E>
  2.         implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  3. {
  4.     //如果使用默认的无参构造初始容量为0,第一次扩容时容量为10
  5.     private static final int DEFAULT_CAPACITY = 10;
  6.     //没有元素时使用空数组,两者区别是啥?
  7.     private static final Object[] EMPTY_ELEMENTDATA = {};
  8.     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  9.     //List底层使用Object数组实现
  10.     transient Object[] elementData;
  11.     //Object数组中实际使用的大小
  12.     private int size;
  13.     public ArrayList() {
  14.         //默认空数组
  15.         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  16.     }
  17. }
复制代码
3.3. add方法


  • 不扩容O(1),扩容O(N)
  1. public boolean add(E e) {
  2.         //确保容量足够
  3.     ensureCapacityInternal(size + 1);  // Increments modCount!!
  4.     //在默认赋值
  5.     elementData[size++] = e;
  6.     return true;
  7. }
复制代码
3.3.1. 确保容量足够容纳新的元素
  1. private void ensureCapacityInternal(int minCapacity) {
  2.     ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
  3. }
  4. private static int calculateCapacity(Object[] elementData, int minCapacity) {
  5.     //使用默认的无参构造方法创建的容量为0,那么第一次扩容为10个
  6.     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  7.         return Math.max(DEFAULT_CAPACITY, minCapacity);
  8.     }
  9.     return minCapacity;
  10. }
  11. private void ensureExplicitCapacity(int minCapacity) {
  12.     modCount++;
  13.     // overflow-conscious code
  14.         //减法比较大小防止溢出
  15.     if (minCapacity - elementData.length > 0)
  16.             //需要扩容
  17.         grow(minCapacity);
  18. }
  19. private void grow(int minCapacity) {
  20.     // overflow-conscious code
  21.     int oldCapacity = elementData.length;
  22.     //新长度=原长度*1.5
  23.     int newCapacity = oldCapacity + (oldCapacity >> 1);
  24.     //新长度<最小需要的长度,那么取最小需要的长度
  25.     if (newCapacity - minCapacity < 0)
  26.         newCapacity = minCapacity;
  27.    
  28.     //新长度比数组最大限制长度还大private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
  29.     if (newCapacity - MAX_ARRAY_SIZE > 0)
  30.             //转而使用最小需要的长度
  31.         newCapacity = hugeCapacity(minCapacity);
  32.     // minCapacity is usually close to size, so this is a win:
  33.     //复制原数组的元素到新数组中
  34.     elementData = Arrays.copyOf(elementData, newCapacity);
  35. }
  36. private static int hugeCapacity(int minCapacity) {
  37.         //最小需要的长度也溢出了,OOM
  38.     if (minCapacity < 0) // overflow
  39.         throw new OutOfMemoryError();
  40.     //最小需要的长度比数组最大限制长度大则使用Integer.MAX_VALUE,否则使用数组最大限制长度
  41.     return (minCapacity > MAX_ARRAY_SIZE) ?
  42.         Integer.MAX_VALUE :
  43.         MAX_ARRAY_SIZE;
  44. }
复制代码
3.3.2. 把元素放入数组最后一个位置
  1. //没啥好说的,赋值然后下标+1
  2. elementData[size++] = e;
复制代码
3.4. remove方法【按下标删除元素】


  • O(N)
  1. public E remove(int index) {
  2.         //防止下标越界
  3.     rangeCheck(index);
  4.     modCount++;
  5.     //保存要删除的数以便返回
  6.     E oldValue = elementData(index);
  7.         //需要移动的个数
  8.     int numMoved = size - index - 1;
  9.     if (numMoved > 0)
  10.             //后面的往前移
  11.         System.arraycopy(elementData, index+1, elementData, index,
  12.                          numMoved);
  13.      //其实数组大小没有变化。当然size属性必须更新的
  14.     elementData[--size] = null; // clear to let GC do its work
  15.     return oldValue;
  16. }
复制代码
3.4.1. 把数组index位置之后的数据往前挪
  1. //计算index后面需要移动的个数
  2. int numMoved = size - index - 1;
  3. if (numMoved > 0)
  4.         //后面的往前移
  5.     System.arraycopy(elementData, index+1, elementData, index,
  6.                      numMoved);
复制代码
3.4.2. 更新size【数组不缩容】
  1. //其实数组大小没有变化。当然size属性必须更新的
  2. elementData[--size] = null; // clear to let GC do its work
复制代码
3.5. remove方法【按元素内容删除】


  • O(N)
  1. public boolean remove(Object o) {
  2.     //为null
  3.     if (o == null) {
  4.             //找到下标
  5.         for (int index = 0; index < size; index++)
  6.             //==null判断
  7.             if (elementData[index] == null) {
  8.                     //删除该下标的元素
  9.                 fastRemove(index);
  10.                 return true;
  11.             }
  12.     } else {
  13.         for (int index = 0; index < size; index++)
  14.             //equals判断
  15.             if (o.equals(elementData[index])) {
  16.                 fastRemove(index);
  17.                 return true;
  18.             }
  19.     }
  20.     return false;
  21. }
复制代码
3.5.1. 首先找到要删除的元素的下标
  1. for (int index = 0; index < size; index++)
  2. {
  3. //...
  4. }
复制代码
如上无非就时遍历查找,效率O(N),然后按照下标删除,如下

  • fastRemove
  1. private void fastRemove(int index) {
  2.     modCount++;
  3.     //计算移动元素的个数
  4.     int numMoved = size - index - 1;
  5.     if (numMoved > 0)
  6.             //复制到前面
  7.         System.arraycopy(elementData, index+1, elementData, index,
  8.                          numMoved);
  9.         //赋为null,并且size--
  10.     elementData[--size] = null; // clear to let GC do its work
  11. }
复制代码
这段代码同上面的remove方法【按下标删除元素】的逻辑
3.5.2. 把数组index位置之后的数据往前挪
  1. //计算移动元素的个数
  2. int numMoved = size - index - 1;
  3. if (numMoved > 0)
  4.         //复制到前面
  5.     System.arraycopy(elementData, index+1, elementData, index,
  6.                      numMoved);
复制代码
3.5.3. 更新size【数组不缩容】
  1. //赋为null,并且size--
  2. elementData[--size] = null; // clear to let GC do its work
复制代码
4. ConcurrentModificationException

参考:fail-fast.md
  1. public class ConcurrentModificationExceptionTest
  2. {
  3.     public static void main(String[] args)
  4.     {
  5.         List<String> stringList = new ArrayList<>();
  6.         for (int i = 0; i < 1000; i++)
  7.         {
  8.             stringList.add(String.valueOf(i));
  9.         }
  10.         for (String s : stringList)
  11.         {
  12.             stringList.remove(s);//java.util.ConcurrentModificationException
  13.         }
  14.     }
  15. }
复制代码
如上的代码运行过程中会抛出ConcurrentModificationException异常
2.png

4.1. 原因分析

遍历时(get)执行删除操作(remove)会抛出这个异常,这叫做fast-fail机制
这是modCount和expectedCount不相等导致的
4.2. 解决

使用fail-safe的JUC包下的CopyOnWriteArrayList

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册