ArrayList源码解析

初始化

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

操作ArrayList

  • add(Object o)

    // 用于存储数据的数组,add方法添加的对象就放在这里面
    transient Object[] elementData;
    // 记录数组的长度
    private int size; 
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!确保内部容量,传递给ensureCapacityInternal的是新list的长度
        // 上一步没报异常,证明ArrayList的容量支持这次add操作
        elementData[size++] = e;// 把对象放进数组
        return true;
    }
    
    • ensureCapacityInternal 确保内部容量

      /**
       * @param minCapacity 新list的长度
       */ 
      private void ensureCapacityInternal(int minCapacity) {
          ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
      }
      
      • calculateCapacity

        就上述的ArrayList初始化代码可知,第一次调用用add,elementDataDEFAULTCAPACITY_EMPTY_ELEMENTDATA一定指向堆中同一地址。
        这个方法会返回一个整数——初始化ArrayList的容量大小,如果minCapacity小于DEFAULT_CAPACITY,返回10,反之返回minCapacity

        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        private static final int DEFAULT_CAPACITY = 10;// 初始容量
        
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
              /**
               * 判断elementData与 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是否指向同一
               * 堆内存空间(==用来比较内存地址)
               */ 
              if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                  return Math.max(DEFAULT_CAPACITY, minCapacity);
              }
              return minCapacity;
        }
        
      • ensureExplicitCapacity

        protected transient int modCount = 0;
        
        private void ensureExplicitCapacity(int minCapacity) {
          modCount++;// 计数,每调用一次ensureExplicitCapacity自增1
        
          // overflow-conscious code 新长度 减去 当前数组长度 大于 0 则调用grow方法
          if (minCapacity - elementData.length > 0)
              grow(minCapacity);
          }
        
      • grow

        private static final int MAX_ARRAY_SIZE = Integer. MAX_VALUE - 8;// 2147483639
        
        private void grow(int minCapacity) {
          // overflow-conscious code
          int oldCapacity = elementData.length;//获取当前数组长度
          int newCapacity = oldCapacity + (oldCapacity >> 1);// 新长度 = 旧长度*1.5
          // 新长度和实际扩容长度比较,两者相减小于0证明新长度不够满足实际所需扩容后总长度的需要
          if (newCapacity - minCapacity < 0)
              newCapacity = minCapacity;// 把实际所需扩容后总长度赋值给新长度
           // 新长度和最大容量比较,两者相减大于0证明这已经是一个大数组了,调用hugeCapacity
          if (newCapacity - MAX_ARRAY_SIZE > 0)
              newCapacity = hugeCapacity(minCapacity);
          // minCapacity is usually close to size, so this is a win:
          // 通常是不会走进上述的两个if中的,这时用数组的拷贝的原理,对ArrayList进行扩容并增加数据
          elementData = Arrays.copyOf(elementData, newCapacity);
          }
        
          private static int hugeCapacity(int minCapacity) {
          if (minCapacity < 0) // overflow 实际所需扩容后总长度小于0,抛异常
              throw new OutOfMemoryError();
          /**
           * 判断实际所需扩容后总长度大于最大容量限制吗?这一步是为了限制扩容超出范围,ArrayList最大容量
           * Integer.MAX_VALUE=2147483647,就是说最大允许存入2147483647个对象。MAX_ARRAY_SIZE是一个尝试分配* 数组大小的上限,并不是ArrayList实际能够存储的元素数量的上限。在需要扩容超过MAX_ARRAY_SIZE的情况
           * 下,ArrayList会尝试使用Integer.MAX_VALUE作为新的容量,但请注意,这并不意味着ArrayList可以真正存* 储这么多元素,因为这将需要大量的连续内存,并且可能受到JVM堆大小的限制
          */
          return (minCapacity > MAX_ARRAY_SIZE) ? 
              Integer.MAX_VALUE :
              MAX_ARRAY_SIZE;
          }
        
      • copyOf

        public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
          @SuppressWarnings("unchecked")
          // 判断数据存储的数据类型,并生成一个新数组返回
          T[] copy = ((Object)newType == (Object)Object[].class)
              ? (T[]) new Object[newLength]
              : (T[]) Array.newInstance(newType.getComponentType(), newLength);
          // 调用本地方法,将旧数组的数据拷贝到新数组,并增加新元素
          System.arraycopy(original, 0, copy, 0,
                           Math.min(original.length, newLength));
          return copy;
        }
        
        public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);
        
  • remove

    public E remove(int index) {
        // 判断下标是否越界
        rangeCheck(index);
    
        modCount++;
        E oldValue = elementData(index);//获取待移除的元素
    
        int numMoved = size - index - 1;// 计算偏移量
        // 偏移量大于0,证明移除的是数组中间的元素
        if (numMoved > 0)
        // 调用本地方法,将旧数组的数据拷贝到新数组
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        // 因为删除了一个元素,并且上一步将元素向前排,那么最后一个元素肯定就不需要了,置为null,这块内存会由GC来回收
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;//返回移除元素后的新数组
    }
    

    rangeCheck

    private void rangeCheck(int index) {
    // 如果 待移除的元素的下标 大于 当前ArrayList的size,抛出异常
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    

由源码可得出结论:ArrayList是有容量限制的,最大值为MAX_VALUE=2147483647,添加元素时ArrayList的处理方式是生成新数组,把旧数组的数据拷贝进去,最后JVM通过垃圾回收机制回收旧数组所占内存,删除元素时ArrayList的处理方式是在原数组基础上做变更。ArrayList的源码中没有加锁,说明ArrayList是线程不安全类。

posted @   勤匠  阅读(2)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示