菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
74
0

【源码解析】扒开ArrayList的外衣

原创
05/13 14:22
阅读数 1308
积千里跬步,汇万里江河;每天进步一点点,终有一天将成大佬。

本文内容

当然ArrayList里的方法不止这些,本文主要讲一些常用的方法

方法变量

Arraylist里的方法变量主要有以下几个

1. 构造方法

1.1 有参构造

1.1.1 传入数组的大小

1.1.1.1代码实现
List<String> list=new ArrayList<>(5);
1.1.1.2源码解析

1.1.2 传入一个list对象

其实这个就相当于把传入的list对象里的数据复制到新的ArrayList对象

1.1.2.1 代码实现
List<String> list=new ArrayList<>(Arrays.asList("z","m","h"));
这里用来Arrays工具类里的asList方法,它的源码里是直接返回一个List,有兴趣的可以去看看,这里就不介绍了
1.1.2.2 源码解析

1.2 无参构造

这个比较简单,直接赋值一个空数组

1.2.1代码实现

List<String> list=new ArrayList<>();

1.2.2源码解析

2. add方法

add一般常用的有两个方法,一个就是add(E e)在尾部添加数据,一个就是add(int index,E element)在指定位置插入元素

2.1 add(E e)

这个是Arrayist的主要方法,平时用的也是最多的方法之一,所以源码比较复杂,比较长

2.1.1 代码实现

List<String> list=new ArrayList<>();
list.add("灰灰HK");

2.1.2 源码解析

  • ensureCapacityInternal(int minCapacity)确保数组容量充足

  • calculateCapacity(Object[] elementData, int minCapacity)

  • 再回到ensureExplicitCapacity(int minCapacity)这个方法,这个方法先修改次数加1,然后判断size+1是不是比当前的数组容量大,如果比当前的数组容量大,则进行扩容操作,扩大容量为原数组的1.5倍
比如第二次调用add方法,此时size+1=2, elementData.length=10,为什么等于10呢?因为第一次默认把数组容量从0扩大到了10,这时size+1elementData.length小,就不会进行扩容操作

  • grow(int minCapacity)扩容
这里调用Arrays.copyOf()方法进行复制操作,当进一步深入这个方法时,发现是由System.arraycopy()这个方法实现复制功能的,这个方法由native关键字修饰,表示不是由Java语言实现的,一般是c/cpp实现

2.1.3 小结

到这里,add的方法流程就走完了,其核心步骤:

  • 每次添加元素时判断数组容量是否充足
  • 第一次添加元素,把数组容量扩容到10
  • 扩容时,除第一次,以后的每次扩容为原大小的1.5倍
  • 扩容后调用System.arraycopy()方法把原数组的元素复制到扩容后的新数组

2.2 add(int index, E element)

该方法为在指定位置插入元素,该位置及后面所有元素后移

2.2.1 代码实现

List<String> list=new ArrayList<>();
list.add("hk");
list.add(0,"灰灰");

2.2.2 源码解析

可以看到,这边又用到了System.arraycopy()这个方法
  • rangeCheckForAdd(int index)判断是否越界
这里他是和size对比,而不是和数组的length对比,我个人认为这样第一节省了空间,第二方便后面移动的操作

  • System.arraycopy()拷贝数组
public static native void arraycopy(Object src,  int  srcPos,
                                     Object dest, int destPos,
                                    int length)
  • src 原数组对象
  • srcPos 原数组起始位置
  • dest 目标数组
  • destPos 目标数组起始位置
  • length 复制多少个数据

2.2.3 小结

插入方法其主要步骤如下:

  • 检查插入的位置是否越界
  • 检查数组容量是否充足,不充足进行扩容相关操作
  • 调用System.arraycopy()进行index及后面的元素后移

3. get方法

3.1 get(int index)

3.1.1 代码实现

List<String> list=new ArrayList<>();
list.add("hk");
list.get(0);

3.1.2 源码解析

  • rangeCheck(int index)判断是否越界
get个add方法判断越界的方法是不一样的,这边是index>=size,多了个等于,为什么要多个等于呢?因为数组是从0开始的,而size相当于是开始的从1开始的
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • elementData(int index)直接返回对应下标的数组元素
E elementData(int index) {
    return (E) elementData[index];
}

3.1.3 小结

get方法比较简单,主要步骤为:

  • 检查是否越界
  • 返回对应元素

4. set方法

4.1 set(int index, E element)

4.1.1 代码实现

List<String> list=new ArrayList<>();
list.add("hk");
list.set(0,"灰灰");

4.1.2 源码解析

5. remove方法

5.1 remove(int index)

5.1.1 代码实现

List<String> list=new ArrayList<>();
list.add("hk");
list.remove(0);

5.1.2 源码解析

当删除的元素为最后一个元素时,numMoved就小于0了,就不会进行移动元素的操作

5.2 remove(Object o)

这个方法在实际中用的比较少,因为AraryList是可以保存重复的元素,所以删除是删除最早添加的元素

5.2.1 代码实现

List<String> list=new ArrayList<>();
list.add("hk");
list.remove("hk");

5.2.2 源码解析

  • 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; // clear to let GC do its work
}

6. clear方法

6.1 clear()

6.1.1 代码实现

List<String> list=new ArrayList<>();
list.add("hk");
list.clear();

6.1.2 源码分析

总结

ArrayList底层扩容或者移动数组元素时都调用了System.arraycopy()来进行相关操作,平时进行我们进行数组复制或移动的时候也可以调用这个方法了,这个性能比循环复制性能高多了,特别是在大量数据的时候。

文章好几次出现了modCount++这个操作,这个modCount主要用户内部类的迭代器

发表评论

0/200
74 点赞
0 评论
收藏