ArrayList源码阅读

主要内容:

  1. arraylist的本质?
  2. arraylist在添加元素的时候如何给数组进行合理的扩容?
  3. arraylist删除某个元素是怎么删除的?
  4. arraylist可以存null和重复值吗?
  5. arraylist的序列化机制

add

    /**
     * 构造一个初始容量为10的空列表。
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

如上所示,使用无参构造器创建的话会初始化初始容量为10的空列表。

    /**
     * 注释意思大概是这个数组在第一次使用add方法的时候才会被赋初始容量大小10
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

所以我们来看看add方法中做了什么:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 这个size默认是0
        elementData[size++] = e;
        return true;
    }

在执行了ensureCapacityInternal(size + 1)之后就开始为先前的空数组elementData赋值了,那么猜测数据容量初始化肯定是在ensureCapacityInternal这个方法中完成的,我们进入这个方法:

    /**
     * 我们可以看到这里做了个if判断,毋庸置疑是true
     */
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //minCapacity值是1,DEFAULT_CAPACITY值是10,这样写的意思
            //就是当你初始化list时设置的长度(包括不写长度的情况下)小于
            //10时,会将list最小容量设置为10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

当需要的长度大于原来数组长度的时候就需要扩容了,相反的则不需要扩容 

点击强制步入进入grow方法:

    /**
     * 增加容量,以确保它至少可以容纳
     * 由最小容量参数指定的元素数量。
     *
     * @param minCapacity 所需的最小容量
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 用位运算确保newCapacity始终大于oldCapacity,以1.5的速度扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 使得newCapacity始终大于或等于10
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 当新容量大于数组最大长度时,这个方法会在minCapacity
        // 大于数组最大长度时返回Integer的最大值,小于就返回数组
        // 最大长度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity通常接近于size,所以这是一个优势
        // 扩容之后空位补全为0
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //对minCapacity和MAX_ARRAY_SIZE进行比较
        //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
        //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

remove

    /**
     * 从列表中删除指定元素的第一个匹配项,
     * 如果它存在。如果列表不包含该元素,则它包含
     * 不变
     *
     * @param o 元素将从此列表中删除(如果存在)
     */
    public boolean remove(Object o) {
        //因为arraylist可以存null
        if (o == null) {
            //遍历整个数组,拿到第一个值为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; // 将数组中最后一个元素设置为null,交给GC来自动清除
    }

可以看出arraylist删除数组中的某个元素其实本质就是移动数组,要移除的元素越在前面,需要移动的元素就越多,开销就越大

toArray

    /**
     * 会创建一个新的数组,你可以随意修改它而并不会导致原来的arraylist发生变动
     *
     * @return 包含列表中所有元素的数组
     * 正确的序列
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

transient

     //将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。
     transient Object[] elementData;

为什么在elementData上添加这个关键字呢?我们来看看arraylist的writeObject和readObject方法:

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        ...
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        ...
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;
        ...
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
        ...
    }

这个size是数组有实际意义数据的真实长度 

为什么不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页

打赏

myllxy

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者