《好好学习》之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();
    }
}
相关推荐
moton20171 小时前
云原生:构建现代化应用的基石
后端·docker·微服务·云原生·容器·架构·kubernetes
何中应1 小时前
Spring Boot中选择性加载Bean的几种方式
java·spring boot·后端
web2u3 小时前
MySQL 中如何进行 SQL 调优?
java·数据库·后端·sql·mysql·缓存
michael.csdn3 小时前
Spring Boot & MyBatis Plus 版本兼容问题(记录)
spring boot·后端·mybatis plus
Ciderw3 小时前
Golang并发机制及CSP并发模型
开发语言·c++·后端·面试·golang·并发·共享内存
Мартин.3 小时前
[Meachines] [Easy] Help HelpDeskZ-SQLI+NODE.JS-GraphQL未授权访问+Kernel<4.4.0权限提升
后端·node.js·graphql
程序员牛肉3 小时前
不是哥们?你也没说使用intern方法把字符串对象添加到字符串常量池中还有这么大的坑啊
后端
烛阴3 小时前
Go 语言进阶必学:&^ 操作符,高效清零的秘密武器!
后端·go
网络风云3 小时前
golang中的包管理-下--详解
开发语言·后端·golang
京东零售技术4 小时前
一次线上生产库的全流程切换完整方案
后端