arraylist最大长度_ArrayList源码分析基于JDK1.8(下)

一、源码总结

在源码类的注释中提到ArrayList用可伸缩数组实现的这个List接口,继承自AbstractList,AbstractList 实现了List接口,提供了List接口的默认实现。ArrayList自身也实现了List接口,实现了对集合的CURD的操作,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标进行快速访问,RandomAccess是一个标记接口,接口内没有定义任何内容。

ArrayList的源码中多次调用了两个数组拷贝的方法,分别是Arrays.copyOfSystem.arraycopy,查看这两个方法的源码。

    public static  T[] copyOf(T[] original, int newLength) {return (T[]) copyOf(original, newLength, original.getClass());
    }
    public static  T[] copyOf(U[] original, int newLength, Class extends T[]> newType) {@SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));return copy;
    }

该方法创建了一个新的数组,然后调用System.arraycopy将元素拷贝到新数组中。

    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,int length);

arraycopy是一个本地方法,通过调用系统的C/C++方法实现。该方法可以保证在同一个数组内的元素的复制和移动。

通过源码的分析,可以更加深刻的理解为什么说ArrayList的查找效率高而增加、删除元素的效率低。

二、扩容

添加元素时使用ensureCapacityInternal()方法来保证容量足够,如果不够时,需要使用grow()方法进行扩容,新容量的大小为 int newCapacity = oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * 增加容量以确保其至少可以容纳最小容量参数指定的元素数量。
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        // oldCapacity 的值为 elementData 中数组长度值
        int oldCapacity = elementData.length;
        // 使用位运算提高运算速度,newCapacity 是 oldCapacity 的 1.5 倍。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果 oldCapacity 的 1.5 倍还比 minCapacity 小,那么 newCapacity = minCapacity
        if (newCapacity - minCapacity 0)
            newCapacity = minCapacity;
        // 如果 oldCapacity 的 1.5 倍比 MAX_ARRAY_SIZE 大,那么调用 hugeCapacity 做点事情
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

        // minCapacity is usually close to size, so this is a win:
        // minCapacity 的值通常接近于 size 的值,所以这就是个胜利(节省空间)

        // 最终 elementData 指向复制了 newCapacity 的新数组对象
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

在添加很多元素前如果知道需要添加的元素数量,尽量先调用ensureCapacity()先扩容数组到指定大小,再进行数据的添加,避免多次调用grow()方法进行扩容。

三、部分方法示例
一、trimToSize()

如果没有新元素添加到集合中,则可以使用此方法最大程度地减少集合的内存开销。

    /**
     * Trims the capacity of this ArrayList instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an ArrayList instance.
     * 调整 ArrayList 实例的 capacity 为列表当前的 size。
     * 程序可以使用这个方法最小化 ArrayList 实例的存储(节省不需要的空间)。
     */
    public void trimToSize() {
        //修改次数加1
        modCount++;
        // 在 size 小于数组的长度(数组中存在 null 引用)前提下
        // 如果 size == 0 ,说明数组中都是 null 引用,就让 elementData 指向 EMPTY_ELEMENTDATA,
        // 否则,就拷贝 size 长度的 elementData 数组元素,再让 elementData 指向这个数组对象
        if (size             elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

ArrayList源码中用size表示实际存储元素的大小,elementData.length表示数组的大小,trimToSize()方法就是最小化 ArrayList 实例的存储(节省不需要的空间),因为ArrayList 只提供了获取size大小的方法,并没有提供获取elementData.length的方法所以并不能很直观的看到该方法的作用,但是可以用debug看到实际变化。

    @Test
    public void test1(){

        ArrayList list = new ArrayList<>(10);
        list.add("1");
        list.add("2");
        list.add("3");
        list.trimToSize();
        list.forEach(System.out::println);
    }

上边方法在和list.trimToSize();上打断点,运行方法跟进debug看到elementData.length的大小为10,size大小为3

98a24f6758531d25bc978d786238ed86.png

方法运行完成后elementData.length的大小为3,size大小为3。

34f165d3fbe6c2ff5c88f63b53381dac.png

trimToSize();执行完成后还可以继续做add()操作,但是并不建议这么做,因为需要重新扩容才能添加元素,所以要在确保不再添加元素时再进行调用该方法。

二、clear()
    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     * 从此列表中删除所有元素。 该调用返回后,该列表将为空。
     */
    public void clear() {
        //修改次数加1
        modCount++;
        
  //方便GC回收将所有元素指向 null
        // clear to let GC do its work
        for (int i = 0; i             elementData[i] = null;
  //大小修改为0
        size = 0;
    }

clear()方法会清除所有元素,这里有个疑问为什么不直接elementData= null;而是将elementData中的每个元素指向null

去网上找了下感觉回答的挺好的原文.

咋一看起来,好像下面的方法更简单:

    modCount++;
    elementData = EMPTY_ELEMENTDATA;
    size = 0;

但是有一个原因让这种做法不可行:elementData不是私有的!

在设计上,为了效率,elementData是包可见的,会被其他类(主要是ArrayList的内部迭代器类)直接引用。如果采用上面的做法,就可能会导致迭代器与数据不一致问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值