美文网首页
ArrayList的一些常见知识点

ArrayList的一些常见知识点

作者: 小北觅 | 来源:发表于2019-07-07 16:37 被阅读0次

    ArrayList是基于数组实现的List类。他可以动态增长和缩减。本文就来总结一下关于ArrayList的面试题, 也是我在之前的面试经历中遇到的。

    一、ArrayList的大小是如何自动增加的?

    当我们向ArrayList的对象中增加一个对象的时候,Java会去检查ArrayList对象,确保底层的数组中有足够的容量来存储这个新的对象。如果容量不够增加新元素的话,那就会新建一个长度更长的数组(1.5倍的oldCapacity),旧的数组就会使用Arrays.copyOf方法被复制到新的数组中去,现有的数组引用指向了新的数组。

    下面进入源码分析一下:

    add方法

    add方法中第一句调用了:
    ensureCapacityInternal(size + 1)
    注意这个size是当前数组中实际的元素个数,而ArrayList中的capacity是数组的长度。打个比方,你可以new一个长度为10的数组,那么capacity就是10,但是这个数组中你只赋值了两个位置,那么size=2。为什么这个方法要size+1呢?因为我们要添加一个新元素啊,当前有size个元素,那么必须要保证数组至少size+1大小才能容得下新添加元素。进入ensureCapacityInternal(size + 1)看看:

    ensureCapacityInternal方法

    调用了ensureExplicitCapacity(int minCapacity)方法,参数是calculateCapacity(Object[] elementData, int minCapacity)。这个方法就是计算一个为了添加新元素的最小容量:

    下面是ensureExplicitCapacity(int minCapacity)方法:

    方法名字也很好理解,就是确保显式的容量。首先增加ArrayList对象的修改次数。然后如果minCapacity-elementData.length>0。说明容量不够了,就调用grow方法去扩容。然后我们就看grow方法是如何扩容:

    可以看到新的容量:int newCapacity = oldCapacity + (oldCapacity >> 1);即1.5倍的oldCapacity。然后用Arrays.copyof()方法把原数组拷贝,最后会用到native的数组拷贝方法。这里注释是overflow-conscious code,表明是考虑到溢出的代码。解释一下:

    note: 我们始终要记得哦,传进来的minCapacity是size+1。

    1.正常情况下,每次capacity扩容1.5倍。当capacity扩大1.5倍发生int的溢出时,就每次进行最小扩容(+1)就可以了。第一个判断 if (newCapacity - minCapacity < 0)这个就是为这个溢出准备的。

    1. 当按照1步骤不断往下走,最后也有可能达到上限值对吧。此时
      发生在第二个判断 if (newCapacity - MAX_ARRAY_SIZE > 0)时,其中MAX_ARRAY_SIZE= Integer.MAX_VALUE-8。这时候就要调用hugeCapacity方法啦:

    至此添加元素之前的准备工作(验证数组大小,必要时的扩容)都做完了。

    by the way,提一下几种数组赋值的效率问题:

    1、System.arraycopy()方法
    2、Arrays.copyOf()方法
    3、for循环,手动复制
    4、clone()方法(效率最低,而且是浅拷贝)

    二、ArrayList or LinkedList如何选择

    访问操作多,插入删除操作少选择 ArrayList。get元素和set元素的时间复杂度O(1)。插入操作默认在数组末尾添加元素,不考虑扩容是O(1)的时间复杂度,在指定位置添加元素,最坏O(n)的时间复杂度。

    访问操作少,插入删除操作多选择LinkedList。

    三、线程不安全

    两个方面:

    3.1 add操作

    上面我们看add元素的源码时,有这么一句:

    elementData[size++] = e;
    

    这句不是一个原子操作,可以分为如下两步执行:

    elementData[size] = e;
    size++;
    

    多线程的情况下可能会导致一个线程覆盖另一个线程的值。时序图如下:

    3.2 扩容时

    ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。

    1. 线程 A 执行完 add 函数中的ensureCapacityInternal(size + 1)挂起了。

    2. 线程 B 开始执行,校验数组容量发现不需要扩容。于是把 新元素E1 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。

    3. 线程 A 接着执行,尝试把 E2 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常 ArrayIndexOutOfBoundsException。

    3.3 如果使用线程安全的ArrayList

    ① 使用synchronize关键字加锁
    ② 使用Collections.synchronizedList
    ③ CopyOnWriteArrayList

    四、ArrayList对象作为方法的参数。

    看如下代码:

    import java.util.ArrayList;
    
    public class TestList {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(4);
    
            System.out.println("调用方法前:");
            System.out.println(list);
            addlist(list);
            System.out.println("#############");
            System.out.println("调用方法后:");
            System.out.println(list);
    
        }
    
        public static void addlist(ArrayList<Integer> list) {
            list.add(5);
        }
    
    }
    

    输出结果:

    把list作为实参传递给方法中,方法中修改list,则原来的list也会跟着改变。这个知识其实是Java的值传递和引用传递的知识点。如果不希望影响原来的,可以这么做。

    import java.util.ArrayList;
    
    public class TestList {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(4);
    
            System.out.println("调用方法前:");
            System.out.println(list);
            addlist(list);
            System.out.println("#############");
            System.out.println("调用方法后:");
            System.out.println(list);
    
        }
    
        public static void addlist(ArrayList<Integer> list) {
            list = new ArrayList<>(list);  //重新new一个对象。
            list.add(5);
        }
    
    }
    
    

    此时结果如下:


    大数据与Java学习资源分享:https://zhuanlan.zhihu.com/p/50030125

    相关文章

      网友评论

          本文标题:ArrayList的一些常见知识点

          本文链接:https://www.haomeiwen.com/subject/rkzvhctx.html