Java 集合 ArrayList 需要知道的几个问题

问:Arraylist 的动态扩容机制是如何自动增加的?简单说说你理解的流程?

答:当在 ArrayList 中增加一个对象时 Java 会去检查 ArrayList 以确保已存在的数组中有足够的容量来存储这个新对象(默认为 10,最大容量为 int 上限 -8,减 8 是为了容错),如果没有足够容量就新建一个长度更长的数组(原来的 1.5 倍),旧的数组就会使用 Arrays.copyOf() 方法被复制到新的数组中去,现有的数组引用指向了新的数组。下面代码展示为 Java 1.8 中通过 ArrayList.add 方法添加元素时,内部会自动扩容,扩容流程如下:

//确保容量够用,内部会尝试扩容,如果需要
        ensureCapacityInternal(size + 1)
        //在未指定容量的情况下,容量为DEFAULT_CAPACITY = 10
        // 并且在第一次使用时创建容器数组,在存储过一次数据后,数组的真实容量至少DEFAULT_CAPACITY
        private void ensureCapacityInternal ( int minCapacity)
        { //判断当前的元素容器是否是初始的空数组
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                //如果是默认的空数组,则 minCapacity 至少为DEFAULT_CAPACITY
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            ensureExplicitCapacity(minCapacity);
        } //通过该方法进行真实准确扩容尝试的操作

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //记录List的结构修改的次数 //需要扩容
        if (minCapacity - elementData.length > 0) grow(minCapacity);
    } //扩容操作

    private void grow(int minCapacity) {
        //原来的容量
        int oldCapacity = elementData.length;
        //新的容量 = 原来的容量 + (原来的容量的一半)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果计算的新的容量比指定的扩容容量小,那么就使用指定的容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果新的容量大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)
        // 那么就使用hugeCapacity进行容量分配
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
        //创建长度为newCapacity的数组,并复制原来的元素到新的容器,完成ArrayList的内部扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
问:请写出下面代码片段的运行结果及原因?
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(2);
        list.add(3);

        Integer[] array1 = new Integer[3];
        list.toArray(array1);
        Integer[] array2 = list.toArray(new Integer[0]);
        System.out.println(Arrays.equals(array1, array2)); //1 结果是什么?为什么?
        
        Integer[] array = {1, 2, 3};
        List<Integer> list = Arrays.asList(array);
        list.add(4); //2 结果是什么?为什么? 
        
        Integer[] array = {1, 2, 3};
        List<Integer> list = new ArrayList<Integer>(Arrays.asList(array));
        list.add(4); //3 结果是什么?为什么?

答:这题的运行结果及原因解释分别如下。

1. 输出为 true,因为 ArrayList 有两个方法可以返回数组 Object[] toArray() 和 <T> T[ ] toArray(T[ ] a),第一个方法返回的数组是通过 Arrays.copyOf 实现的,第二个方法如果参数数组长度足以容纳所有元素就使用参数数组,否则新建一个数组返回,Arrays.equals(array1, array2) 比较的是值,所以结果为 true。

2. 会抛出 UnsupportedOperationException 异常,因为 Arrays 的 asList 方法返回的是一个 Arrays 内部类的 ArrayList 对象,这个对象没有实现 add、remove 等方法,只实现了 set 等方法,所以通过 Arrays.asList 转换的列表不具备结构可变性。

3. 正常运行,不可变结构的 Arrays 的 ArrayList 通过构造放入了真正的万能 ArrayList,自然就可以操作。

问:为什么 ArrayList 的增加或删除操作相对来说效率比较低?能简单解释下为什么吗?

答:ArrayList 在小于扩容容量的情况下其实增加操作效率是非常高的,在涉及扩容的情况下添加操作效率确实低,删除操作需要移位拷贝,效率是低点。因为 ArrayList 中增加(扩容)或者是删除元素要调用 System.arrayCopy 这种效率很低的方法进行处理,所以如果遇到了数据量略大且需要频繁插入或删除的操作效率就比较低了,具体可查看 ArrayList 的 add 和 remove 方法实现,但是 ArrayList 频繁访问元素的频率是非常高的。遇到增加或删除的场景我们应该尽可能使用 LinkedList 进行替代效率会高一些。

问:简单说说 Array 和 ArrayList 的区别?

答:

  • Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型;

  • Array 的大小是固定的,ArrayList 的大小是动态变化的;

  • ArrayList 提供了更多的方法和特性,譬如 addAll()、removeAll()、iterator() 等。

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

相关阅读更多精彩内容

友情链接更多精彩内容