《好好学习》之ArrayList

1. ArrayList 简介

ArrayList 的底层是数组队列,相当于动态数组,它的容量能动态增长,线程不安全。

ArrayList继承于 AbstractList ,实现了 List, RandomAccess , Cloneable, java.io.Serializable 这些接口。

ArrayList扩容是1.5倍机制:初始是10,第一次扩容后就是10+5

arduino 复制代码
/**
 * 首次初始化时,集合容量是10
 */
private static final int DEFAULT_CAPACITY = 10;

2.ArrayList --增

实例化时是个没有容量的空数组,首次调用add方法,容量扩容成默认容量10。

scss 复制代码
    public boolean add(E e) {
        //计数、扩容方法
        ensureCapacityInternal(size + 1);  
        //设值
        elementData[size++] = e;
        return true;
    }


    private void ensureCapacityInternal(int minCapacity) {
    	//首次新增会进入此if,获取初始化容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 当真实数据数量大于容量时会触发下面扩容方法
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    
    private void grow(int minCapacity) {
        // 暂存旧的容量
        int oldCapacity = elementData.length;
        // 计算新的容量:10+10>>1 = 15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // MAX_ARRAY_SIZE这个数值很大,正常业务代码中,很难触发这个if,可以不关注   
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 使用数组拷贝方法获得扩容后新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3.ArrayList --删

java 复制代码
// 根据值删除
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
	//具体解释看下面根据下标删除即可
    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
    }

//根据下标删除
    public E remove(int index) {
    //校验传入下标是否超过size(size代表数据具体数值数量)
        rangeCheck(index);

        modCount++;
        //获取当前传入的下标对应元素值
        E oldValue = elementData(index);
		//复制的位数
        int numMoved = size - index - 1;
        //大于0才会移动数组,正好等于0时代表你删除的是最后一位数据,所以也不需要移动数组
        if (numMoved > 0)
            /**
             * System.arraycopy的方法原型:调用此方法后结果就是index后的数据全部往前移动一位
             * @param src       要复制的源数组
             * @param srcPos    源数组要复制的起始位置(从0开始)
             * @param dest      要复制的目标数组
             * @param destPos   目标数组的起始位置(从0开始)
             * @param length    要复制的长度
            */
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // clear to let GC do its work 数值置为null,断开强引用,等待gc回收
        elementData[--size] = null; 

        return oldValue;
    }

4.ArrayList --查

scss 复制代码
public E get(int index) {
//校验传入下标是否超过size(size代表数据具体数值数量)
    rangeCheck(index);
//根据下标获取数组中对应的值返回
    return elementData(index);
}

5.ArrayList --非迭代器删除方法报错原因

php 复制代码
for (String a : list) {
    list.remove(a);
}

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at TestString.main(TestString.java:22)
	

遍历时会调用内部类Itr->next方法

arduino 复制代码
final void checkForComodification() {
//此方法会校验modCount和expectedModCount一定要相同才能成功
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

而集合自带的删除方法,只会递增modCount,却不会更新expectedModCount,所以第二个元素删除时就会报错

我们需要调用迭代器里面的删除方法,就不会再报错,方法如下:

csharp 复制代码
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        //重要的是这句代码,会同步modCount值给expectedModCount
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}
相关推荐
Vcats5 分钟前
深入浅出:基于SpringBoot和JWT的后端鉴权系统设计与实现
java·spring boot·后端
~kiss~7 分钟前
Rust~二刷异步逻辑
开发语言·后端·rust
SomeB1oody15 分钟前
【Rust中级教程】2.7. API设计原则之灵活性(flexible) Pt.3:借用 vs. 拥有、`Cow`类型、可失败和阻塞的析构函数及解决办法
开发语言·后端·性能优化·rust
larance24 分钟前
Flask 发送邮件
后端·python·flask
Aska_Lv30 分钟前
从零到一写组件库-日志组件库
后端
ybq195133454311 小时前
javaEE-SpringBoot日志
java·spring boot·后端
PyAIGCMaster1 小时前
第二周补充:Go语言中&取地址符与fmt函数详解
开发语言·后端·golang
Dongwoo Jeong1 小时前
缓存基础解释与缓存友好型编程基础
后端·c·cache·cache friendly
Gy-1-__1 小时前
【springcloud】快速搭建一套分布式服务springcloudalibaba(一)
后端·spring·spring cloud
硬件人某某某1 小时前
基于Django的手办交易平台~源码
后端·python·django