ArrayList看完你就明白了

JDK版本:1.8


图0:继承结构上下文

我们进入ArrayList类查看类结构如下:


图1:ArrayList的结构和常用

类的结构已经明确,我们以下内容就分为两个部分,介绍构造函数和方法

构造函数

我们先看这个空构造
List<String> list = new ArrayList<String>();
//定义一个空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//定义数组中的元素 transient 表明该变量不支持序列化
 transient Object[] elementData;

    /**
     * Constructs an empty list with an initial capacity of ten.
       上面的英文翻译个人认为不对,上来的时候DEFAULTCAPACITY_EMPTY_ELEMENTDATA只是一个空数组,并不涉及到数组有多大
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList内部是由数组实现,elementData这个object类型的数组即为其存储的数据结构

再看这个有参构造
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

传入参数initialCapacity大于0时就将elementData初始化为一个initialCapacity大小的数组
传入参数initialCapacity等于0时和无参时保持一致
传入参数initialCapacity小于零时抛出IllegalArgumentException异常

构造函数先看到这里...比较简单

常用方法

size()
//数组中包含的元素多少
private int size;
public int size() {
        return size;
    }

当然size的数量肯定是在add或者remove时动态修改的

add()
    /**
     * 向数组的末尾添加一个元素
     */
    public boolean add(E e) {
        //检测是否需要并扩容 说白了就是添加一个元素之前 看看容量够不够
        //添加新元素是比对容器是不是够大 你看它用size+1 多线程就会出现问题
        // size的值在其它线程中可变
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
//判断元素是否是首次添加
    /**
     * 默认扩容大小.
     */
private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
        //判断elementData 中是否包含数据  也意为是否为首次添加
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //首次初始化数组大小时,默认给10 找不到为啥要比一下 首次添加元素 minCapacity肯定为1
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
       //此处的minCapacity为目标容量
        ensureExplicitCapacity(minCapacity); 
    }

首次添加元素嘛,首次的话也不会直接初始化容量为1的数组 而是直接来个大小为10的数组

//数组的修改次数
protected transient int modCount = 0;
//判断容器本身容量还够不够
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        // 容器本身容量和目标容量做对比 判断是否需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

目标容量和本身容量作比较,如果本身容量不够了 就扩容

 /**
     * 扩容
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // 本人容量 首次为0
        int oldCapacity = elementData.length;
       // 扩容原则是 老容量的一半 7的一半是3
        int newCapacity = oldCapacity + (oldCapacity >> 1);
       // 扩容完后的值能不能够用
        if (newCapacity - minCapacity < 0)
       // 不够用 直接用目标容量
            newCapacity = minCapacity;
       //最后的容量是不是比 最大整形还大
        if (newCapacity - MAX_ARRAY_SIZE > 0)
       //如果太大最高也就给最大整型-8的值 适配其他虚拟机 为啥要-8 其他vm整型头信息可能会有别的用
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

整个过程如下:
你想找一个盆儿盛水
你妈先不管你要多少水 先给你个盆儿 这个盆儿肯定是咱平时能盛水用的
你要往盆儿里加水
麻麻得看盆儿有没有地儿装,如果不够了就再找个比现在盆儿1.5倍的盆儿(如果还不够就再找个肯定够的盆儿)
看看是不是装的水太多了,如果太多 最多给你一个缸

addAll()
 public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        //日常扩容 详见上面
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //原数组 a,开始索引0,目标数组elementData,开始位置size(本来就比索引多1),numNew全挪
        System.arraycopy(a, 0, elementData, size, numNew);
        //新老数组的合
        size += numNew;
        return numNew != 0;
    }

两个数组叠在一起的感觉
我有两个棍插在洞里依次排列 | |
你有三个棍插在洞里依次排列 | | |
你想和我混成一排
System.arraycopy(a, 0, elementData, size, numNew);
a为你的那排棍
0为你想从你棍的第几个开始 和我的混到一起
elementData为我那排棍
size 为接纳你棍的起始位置
numNew为 为你预备出来几个洞

remove()

//数组的修改次数
protected transient int modCount = 0;
public boolean remove(Object o) {
        //判断是否list.remove(null) 遍历数组 寻找为null的值
        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 remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        //被修改的次数加1
        modCount++;
       //size -1 看成转换为索引 总索引-被删除的值所在的索引 为剩余元素条目数
        int numMoved = size - index - 1;
       //如果剩余条目数大于0 则证明删完后需要挪后面剩下的元素
        if (numMoved > 0)
           //参数分别为:原数组、需要挪动的启示位置、挪动后的数组、需要挪到的位置、挪几个
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //挪完后 少了个元素 最后一个肯定变成了空
        elementData[--size] = null; // clear to let GC do its work
    }

有五个孔, 圆木棍插在 孔中,依次排列了5个
| | | | |
此时想删掉第二个,因此 找到第二个并拔出来,将第二个后面的木棍依次向前面挪
肯定剩了最后一个孔,拿土埋上

以上是数组的一些常用方法,其中的方方面面都是实际生活中的缩影

备注:ArrayList非线程安全 如果想线程安全 请使用Collections.synchronizedList(new ArrayLsit())包装一下

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容