Java ArrayList源码深入分析

作者:niuyongzhi 时间:2023-06-16 16:30:26 

1.ArrayList 是基数组结构的,需要连续的内存空间

从构造函数可以看出,ArrayList内部用一个Object数组来保存数据。

对于无参构造,ArrayList会创建一个大小为10的初始数组,第一次扩容时会创建默认大小数组。

//无参构造函数,会构造一个空数组。第一次扩容时会创默认大小为10的数组
   private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
   transient Object[] elementData;
   public ArrayList() {
       this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   }
   private static final Object[] EMPTY_ELEMENTDATA = {};
   //如果给定集合大小,会创建一个对应大小的数组
   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);
       }
   }

2.对于set和get方法ArrayList效率高,因为可以直接通过下标获取存储对象。

//直接通过角标从数组中取出元素
   public E get(int index) {
       if (index >= size)
           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
       return (E) elementData[index];
   }
   //set 方法,直接通过index 拿到元素,进行修改就行。
   public E set(int index, E element) {
       if (index >= size)
           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
       E oldValue = (E) elementData[index];
       elementData[index] = element;
       return oldValue;
   }

3.对于add方法,有两种情况:

第一种是在尾部添加,不需要移动数组。

//Add 方法 ,在末端插入不需要
   public boolean add(E e) {
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
   }

第二种情况在指定位置添加或插入数据,则需要移动数组,效率比较低。如果插入在第0个位置则需要移动整个数组。

public void add(int index, E element) {
       if (index > size || index < 0)
           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
       ensureCapacityInternal(size + 1);  // Increments modCount!!
       // * 入的元素在index。则移动的数组应该从index开始,向后移动一个,
//移动的数组长度 size-index 从index开始的数组长度
       System.arraycopy(elementData, index, elementData, index + 1,
               size - index);
       elementData[index] = element;
       size++;
   }

在add时还需要考虑到数组的扩容问题,看ensureCapacityInternal方法

private void ensureCapacityInternal(int minCapacity) {
       //通过无参构造创建为设置初始大小时,判断条件为true
       if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           //创建默认大小的数组,初始大小是10
           minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
       }
       ensureExplicitCapacity(minCapacity);
   }

添加元素后,元素数量大于数组长度时,进行扩容grow,扩容大小是原数组的1.5倍

private void ensureExplicitCapacity(int minCapacity) {
       modCount++;
       // overflow-conscious code
       //新增后,数组容量已经大于了数组长度,则进行扩容
       if (minCapacity - elementData.length > 0)
           grow(minCapacity);
   }
   private void grow(int minCapacity) {
       // overflow-conscious code
       int oldCapacity = elementData.length;
//新的数组大小是原来的1.5倍
       int newCapacity = oldCapacity + (oldCapacity >> 1);  
       if (newCapacity - minCapacity < 0)
           newCapacity = minCapacity;
       //创建一个新长度的数组。
       elementData = Arrays.copyOf(elementData, newCapacity);
   }

4.对应remove 删除元素操作。如果删除元素在最后一个,不需要移动数组,否则会将被删除元素之后的所有元素都要向前移动,效率比较低。

//删除
   public E remove(int index) {
       if (index >= size)
           throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
       modCount++;
       //取出被删除的元素
       E oldValue = (E) elementData[index];
       int numMoved = size - index - 1;
//移动的数组长度,大于0才需要移动,小于0说明被删元素在最后一个
       if (numMoved > 0)
           //第一个参数是源数组,
           //第二个参数是移动的起始位置:index+1,从被删元素的后一个位置开始
           //第三个参数是目标数组
           //第四个参数是移动目标位置:index,被删除元素的位置
           //第五个参数是移动的数组长度 size - index - 1,从index+1位置开始之后的数组长度
           //最终会调用到native方法进行移动。
           System.arraycopy(elementData, index+1, elementData, index,numMoved);
       elementData[--size] = null; // clear to let GC do its work
       return oldValue;
   }

5.System.arraycopy方法参数分析。

Java ArrayList源码深入分析

第二个参数 index+1是被移动数组的起始位置,即被删元素后一个位置

第四个参数index,是被移动数组的起始位置,即被删元素的位置。

最后一个参数size-index-1,被移动数组的长度,即被删元素后一个位置到元素末尾的长度

public static native void arraycopy(java.lang.Object src, int srcPos, java.lang.Object dest, int destPos, int length);

通过对源码的阅读,也许会对ArrayList有一个更加直接的了解。

来源:https://blog.csdn.net/niuyongzhi/article/details/125814050

标签:Java,ArrayList,源码
0
投稿

猜你喜欢

  • Android开发之TabHost选项卡及相关疑难解决方法

    2022-01-10 06:30:57
  • java为何不能多继承的原因详解

    2023-10-12 04:45:00
  • Java关键字synchronized原理与锁的状态详解

    2021-11-16 05:30:29
  • 基于spring-boot和docker-java实现对docker容器的动态管理和监控功能[附完整源码下载]

    2022-02-04 00:41:18
  • 浅析Java中如何实现线程之间通信

    2022-08-24 14:28:36
  • idea创建javaweb原生项目的实现示例

    2023-06-16 05:40:35
  • Java使用动态规划算法思想解决背包问题

    2022-12-02 03:53:49
  • 使用javafx更新UI的方法

    2023-05-02 17:32:30
  • C#添加Windows服务 定时任务

    2021-10-07 15:12:22
  • C#实现窗体抖动的两种方法

    2021-10-06 10:20:52
  • Android开发之merge结合include优化布局

    2022-07-18 07:22:51
  • C#的通用DbHelper类(支持数据连接池)示例详解

    2022-01-14 11:59:56
  • Android开发必备知识 为什么说Kotlin值得一试

    2022-02-09 08:41:38
  • C#生成PDF文件流

    2023-03-19 08:52:56
  • java编程求二叉树最大路径问题代码分析

    2023-03-16 20:44:16
  • Android自定义View之继承TextView绘制背景

    2021-11-05 11:16:06
  • Flutter实现顶部导航栏功能

    2023-03-10 17:13:48
  • java 文件流的处理方式 文件打包成zip

    2022-07-08 12:43:03
  • JFinal实现伪静态的方法

    2023-07-17 12:11:37
  • Android10 启动Zygote源码解析

    2021-10-11 07:53:13
  • asp之家 软件编程 m.aspxhome.com