ArrayList方法分析及实现原理

ArrayList实现原理

ArrayList本质上为一个动态数组,会根据元素的动态增删改查进行数组大小的变化。以下就常用的方法进行源码的一些个人理解。

目录

构造函数 ArrayList()

  • 构建一个长度为0的动态数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

构造函数 ArrayList(int initialCapacity)

  • 构建一个长度为initialCapacity的数组
this.elementData = new Object[initialCapacity];

add(E e)

Add(E e)中就不把详细源码copy过来了,只是大概的说下其添加元素思路。在add(E e)方法中首先会调用一个叫ensureCapacityInternal(int minCapcaity)的方法进行检测数组长度,如果所需要的数组长度大于当前数组长度(即数组长度不够),那么会调用grow(int minCapacity)方法进行数组扩容,扩容的时候先计算出扩容后的数组长度,具体计算代码如下:

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);

oldCapacity >> 1是将oldCapacity转化为二进制然后再将其右移一位,即实际效果为oldCapacity除2向下取值。也即动态数组每次扩容都是讲长度扩大为原来的1.5倍。

此时,数组已有足够的长度添加元素e,即将e依次添加至数组中。

elementData[size++] = e;
return true;

add(int index, E element)

Add(int index, E element)先通过方法rangeCheckForAdd(index)进行边界检测,若index小于0或者大于数组的长度那么抛出异常IndexOutOfBoundsException。在边界检测之后,同样调用Add(E e)中的ensureCapacityInternal(int minCapcaity)方法进行数组扩容操作。但与Add(E e)不同的是,Add(E e)中扩容之后只需要在数组最后添加元素e,而Add(int index, E element)需要在指定位置index添加元素e。插入逻辑如下:

// System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
index++;

可能有的小伙伴对System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)这个方法不是很了解,这个函数是JNI实现的Native方法。我刚开始看的时候也不太了解,但是当看到该函数的参数注释大家可能就会明白大半。

@param src      the source array
@param srcPos   starting position in the source array.
@param dest     the destination array.
@param destPos  starting position in the destionation data.
@param length   the number of array elements to be copied.

因为是JNI实现的Native方法,所以我也没有再去通过其他途径看里面的源码(我也看不懂JNI代码…),但是通过注释也差不多能猜出大半了。该函数将原数组Object src从第int srcPos位开始,复制length长度到Object dest数组中,并且是从int destPos位开始。

在将数组复制之后,然后再把元素element插入到数组的第index位中去。同时,将数组长度加1。


remove(Object o)

remove(Object o)方法其实也比较简单,当调用它时首先会对Object o进行判断,如果onull的话则对数组进行正序遍历,找到数组中第一个为null的元素的索引值index,然后调用私有函数fastRemove(int index)进行快速移除,重要代码如下:

int numMoved = size - index - 1;
if(numMoved > 0)
  System.arraycopy(elementData, index+1, elementData, index, numMoved);   //将数组从index+1位处全部前移一位
  elementData[--size] = null;

fastRemove(int index函数中忽略边界检测,直接根据index计算出要移动的元素数量numMoved,然后将index+1及其之后的元素均向左移动一位到index开始处的一共numMoved个元素。因为移除了一个元素,所以最后将数组最后一位赋值null让GC回收。因为找到了需要移除的元素并将其成功移除,所以return true

同理,如果o不为null,那么正序遍历找出第一个与Object o相同的元素的索引值index,之后逻辑同上,调用私有函数fastRemove(int index)去进行快速移除。然后return true

若在数组中没有找到需要移除的元素object o,则返回return false


remove(int index)

该方法先进行边界检测,看是否index超出数组长度size,若index在合理范围内则进行与上述方法remove(Object o)一样的逻辑。计算出需要移动元素个数,然后进行元素的移动,最后将最后一位赋值为null


set(int index, E element)

这个方法也很简单,先进行边界检测,确保index不超出数组长度size,然后将元素element赋值给数组第index项。

rangeCheck(index);  // 边界检测
E oldValue = elementData(index);
elementData[index] = element;   //赋值操作
return oldValue;

以上就是ArrayList的几种很基础的函数,归根到底是基于对动态数组的操作。也即ArrayList的底层其实就是一个动态数组,同时有一点需要稍微提及的是,ArrayList是非线程安全的(线程不安全)。那为什么这么说呢,其实在之前的几种方法源码中对每次对动态数组操作的时候都会维护更新一个变量modCount,因为不影响我们理解上述方法,所以我在之前的代码中就省略了对modCount维护更新的那一步。我把源码中的其中一段该变量的注释截取下来可能会帮助大家理解写这个变量的作用是什么。

     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     * ...(以下省略)
     protected transient int modCount = 0;   // 变量modCouont

总体意思大概是说这个数是随着list的结构化修改的时候变化。那什么是结构化修改的时候呢?就是当list的size变化的时候就是结构化修改的时候。并且在迭代过程中打断可能会导致不正确的结果。

在写的过程中会将modCount赋值给一个叫expectedModCount的变量,如果在多线程中进行操作,那么会出现所期望的expectedModCount变量和modCount不同,这样就会抛出ConcurrentModificationException异常。那么要解决这个问题,就需要使用到Java.util.concurrent包中的类了。

同时,由上面所知,ArrayList实质为一个动态数组,对其查找十分迅速,但是如果如要添加和删除就要数组元素相对应的左移或右移,效率低。因此,如果查多改少建议使用ArrayList,如果改多查少那么就可以使用LinkedList了。有关LinkedList的讲解请看下一章的介绍。

以上为个人对源码的理解,如果有不对之处,请大家批评指出。十分感谢~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值