栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ArrayList

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ArrayList

在了解这个类之前,需要先知道为什么会有这个类,它解决了什么问题。ArrayList 的出现是为了解决数组的缺陷,使用 java 的数组需要在创建数组时便确定数组的容量,因为数组本身不会自动扩容。所以对于数组内元素数量不确定的场景,都可以用 ArrayList 来简化代码。

既然 ArrayList 解决了数组扩缩容问题,那么其就相当于是一种创新型的数据结构,对应的它也需要解决好增删查改的问题,后面我们就从增删查改四个维度看一下它的实现方案。

对 ArrayList 的基础操作 增

Add 数据的第一步是创建 ArrayList,其有三个构造函数,分别是用指定容量创建,无参构造创建和使用别的 Collection 来创建新的 ArrayList。这其中如果采用无参构造创建那么默认的 elementData 会被设置成 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这是一个空数组,你会发现它和 EMPTY_ELEMENTDATA 这个空数组被区分开了,其目的是为了判断出如果以无参构造创建 ArrayList,那么第一次添加元素时数组最小容量会被设置为 DEFAULT_CAPACITY,也就是下面这段代码控制。

    private static final int DEFAULT_CAPACITY = 10;
    
    private int newCapacity(int minCapacity) {
        // ...
            // 这里会判断是不是默认容量空数组,是的话最小容量会用 DEFAULT_CAPACITY
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
        // ...
    }

了解完创建的逻辑,接下来就是新增,新增的逻辑中有几个关键变量,分别是 elementData, size, modCount, ArrayList 中使用 elementData 存放数据,size 指针表明当前数组中插入了多少个元素,同时也用于帮助新的数据插入数组尾部,modCount 用于控制数据版本,ArrayList 中只要对 elementData 进行了修改那么便会同时修改 modCount,确保迭代内不出现写入操作,防止迭代时数据出错。

如果在添加元素的时候出现元素长度不够的情况则会进行扩容,扩容时每次扩容 1.5 倍,由下面的代码计算扩容后的容量。

    private int newCapacity(int minCapacity) {
        // ...
        // 这里的 oldCapacity >> 1 可以直接理解为老容量除 2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // ...
    }

在扩容的代码里有一个有意思的方法是 hugeCapacity,仔细看代码会发现这里有一个 MAX_ARRAY_SIZE 常量定义了数组的最大长度为 Integer.MAX_VALUE - 8,之所以定义这个长度为数组的最大长度是因为某些虚拟机会在数组头寸写东西,所以如果超过这个长度就有可能出现 OutOfMemoryError,不过若是超过了这个长度,代码也会以 Integer.MAX_VALUE 去申请试试。

删除方法有三个,分别是按数组下标删除、按指定对象删除和按给定集合删除,第一种方式比较简单,直接通过下标删除元素,不过 ArrayList 内部的实现不是通过循环来实现,而是通过调用 System.arraycopy 方法来删除,效率更高。按对象删除则和第一种方式类似,其会先用循环找到元素下标,然后采用和第一种方式相同的方法删除元素。

最后一种按给定集合删除比较有意思,其内部采用了双指针的方式来删除数据,逻辑是先循环找到要第一个要删除的元素下标,保存该指针,然后第二个指针从该位置继续向后遍历,下一个元素要删除则继续找下一个,如果下一个不用删除则将其拷贝到第一个指针的位置,然后将第一个指针加一,以下是关键源代码。

    boolean batchRemove(Collection c, boolean complement,
                        final int from, final int end) {
        final Object[] es = elementData;
        int r;
        // ...
        // 保存第一个指针为 w,r 为第二个指针
        int w = r++;
        try {
            // 这里便是双指针遍历拷贝数据
            for (Object e; r < end; r++)
                if (c.contains(e = es[r]) == complement)
                    es[w++] = e;
        }
        // ...
        finally {
            modCount += end - w;
            // 最终将尾巴抹成 null
            shiftTailOverGap(es, w, end);
        }
        return true;
    }

查找元素的方法有三个,分别是 get, indexOf, lastIndexOf, get 是直接按照数组下标找元素,indexOf 和 lastIndexOf 则是通过循环找元素然后返回数组下标。

对 ArrayList 的遍历有两种方式,一种是使用数组下标循环遍历,另一种则是使用 ArrayList 提供的迭代器进行遍历,ArrayList 提供了两种迭代器,分别是 Itr 和 ListItr,其中 ListItr 是继承 Itr 并在其中增加了一些方法,使其能够一边遍历一边 Add 元素。需要注意的是使用 Itr 遍历元素不能一边遍历一边修改数组内元素,否则 expectedModCount 与 modCount 不一致会导致报错,而使用 ListItr 则可以一边修改,这是因为 ListItr 修改元素后会更新 expectedModCount。

修改元素直接使用 set 方法,内部会直接根据下标修改数组元素。

Q & A ArrayList 是不是线程安全的

ArrayList 不是线程安全的,判断一个类是不是线程安全是看类本身有没有状态,如果有状态那么还要看在修改这些状态的时候有没有上锁之类的操作保证修改这些状态确保不会出错。

另外如果想让 ArrayList 变成线程安全,可以使用 Collections.synchronizedList 进行包装,拿到的新 List 在 List 方法上均加了锁,需要注意的是新的 List 迭代器依然使用的是原集合的迭代器,所以其迭代器依然是线程不安全的,另一种方式是使用 CopyonWriteArrayList 这个线程安全的 List。

原文链接

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/786407.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号