大厂高频面试题:谈谈你对ArrayList的理解?

面试中经常碰到ArrayList的问题,比如:

  • 请你说说ArrayList和LinkList的区别?
  • 请你谈一下对ArrayList的理解?
  • ArrayList是线程安全的吗?
  • ArrayList<String> list = new ArrayList<>(20); 中的list扩充几次?

本篇就讲一讲ArrayList的知识点。

1.什么是ArrayList?

ArrayList是个动态数组,实现List接口,主要用来存储数据,如果存储基本类型的数据,如int,long,boolean,short,byte,那只存储它们对应的包装类。它的特点是:

增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。

查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。

2.ArrayList线程安全吗?

ArrayList是线程不安全的。

当开启多个线程操作List集合,向ArrayList中增加元素,同时去除元素。最后输出list中的所有数据,会出现几种情况:

①有些元素输出为Null;②数组下标越界异常。

有两种解决方案:

第一种是选用线程安全的数组容器是Vector,它将所有的方法都加上了synchronized。
public static Vector<Object> vector= new Vector<Object>(); 
第二种是用Collections.synchronizedList将ArrayList包装成线程安全的数组容器。
List<String> list = Collections.synchronizedList(new ArrayList<>());

为什么ArrayList线程不安全,我们还使用它?

因为大多数的场景中,查询操作使用频率高,增删操作的使用频率低。如果涉及频繁的增删,可以使用LinkedList,实际开发过程中还是ArrayList使用最多的。 不存在一个集合既查询效率高,又增删效率高,还线程安全的,因为数据结构的特性就是优劣共存的,想找个平衡点很难,牺牲了性能,那就安全,牺牲了安全那就快速。

3.ArrayList默认的数组大小是多少?

private static final int DEFAULT_CAPACITY = 10;

ArrayList默认数组大小是10。

public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }

默认的构造方法,构造一个初始容量为10的空列表。

4.ArrayList如何扩容?

ArrayList的扩容主要发生在向ArrayList集合中添加元素的时候,通过add()方法添加单个元素时,会先检查容量,看是否需要扩容。如果容量不足需要扩容则调用grow()扩容方法,扩容后的大小等于扩容前大小的1.5倍,也就是10+10/2。比如说超过10个元素时,会重新定义一个长度为15的数组。然后把原数组的数据,原封不动的复制到新数组中,这个时候再把指向原数的地址换到新数组。
// grow扩容方法
private void grow(int minCapacity) {
    // 记录扩容前的数组长度
    int oldCapacity = elementData.length;

    // 位运算,右移动一位。 整体相当于newCapacity =oldCapacity + 0.5 * oldCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 如果扩容后的长度小于当前的数据量,那么就将当前的数据量的长度作为本次扩容的长度
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 判断新数组的长度是否大于可分配数组的最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 将扩容长度设置为最大可用长度
        newCapacity = hugeCapacity(minCapacity);
    // 拷贝,扩容,构建一个新的数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

5.ArrayList<String> list = new ArrayList<>(20); 中的list扩充几次?

不需要扩容。当指明了需要多少空间时,会一次性分配这么多的空间,就不需要扩容了。
 public ArrayList(int initialCapacity) {
        //判断initialCapacity是否大于0
        if (initialCapacity > 0) {
        //创建一个数组,且指定长度为initialCapacity
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
       //如果initialCapacity容量为0,把EMPTY_ELEMENTDATA的地址赋值给elementData
            this.elementData = EMPTY_ELEMENTDATA;
       // 如果初始容量小于0,则会出现 IllegalArgumentException 异常
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

6.ArrayList频繁扩容导致添加性能急剧下降,如何处理?

使用ArrayList时,可以 new ArrayList(大小)构造方法来指定集合的大小,以减少扩容的次数,提高写入效率。

7.ArrayList插入或删除元素一定比LinkedList慢么?

取决于删除的元素离数组末端有多远,ArrayList拿来作为堆栈来用还是挺合适的,push和pop操作完全不涉及数据移动操作。

8.如何复制某个ArrayList到另一个ArrayList中去?

(1)使用clone()方法
public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("云");
        list.add("烟");
        list.add("成");
        list.add("雨");
        Object o = list.clone();
        System.out.println(o);
        System.out.println(list);
    }


运行结果
源码分析:
 public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }


(2)使用ArrayList构造方法
 public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        ArrayList<String> list1 = new ArrayList<>(list);
        for (String s : list1) {
            System.out.println(s);
        }
    }


运行结果
源码分析:
  public ArrayList(Collection<? extends E> c) {
       // 将集合构造中的集合对象转成数组,且将数组的地址赋值给elementData
        elementData = c.toArray();
       // 将elementData的长度赋值给 集合长度size,且判断是否不等于 0
        if ((size = elementData.length) != 0) {
            // 判断elementData 和 Object[] 是否为不一样的类型
            if (elementData.getClass() != Object[].class)
                //如果不一样,使用Arrays的copyOf方法进行元素的拷贝
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 用空数组代替
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }


(3)使用addAll方法
  public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("world");
        list.add("!");
        ArrayList<String> list1 = new ArrayList<>();
        list1.addAll(list);
        System.out.println(list);
        System.out.println(list1);
    }


运行结果
源码分析:
 public boolean addAll(Collection<? extends E> c) {
        //把集合的元素转存到Object类型的数组中
        Object[] a = c.toArray();
        //记录数组的长度
        int numNew = a.length;
        //调用方法检验是否要扩容,且让增量++
        ensureCapacityInternal(size + numNew); 
        //调用方法将a数组的元素拷贝到elementData数组中
        System.arraycopy(a, 0, elementData, size, numNew);
        //集合的长度+=a数组的长度
        size += numNew;
        //只要a数组的长度不等于0,即说明添加成功
        return numNew != 0;
    }


9.ArrayList用来做队列合适么?

队列一般是FIFO(先入先出)的,如果用ArrayList做队列,需要在数组尾部追加数据,数组头部删除数组,反过来也可以。但是无论如何总会有一个操作会涉及到数组的数据搬迁,这个是比较耗费性能的。

虽然ArrayList不适合做队列,但是数组是非常合适的。比如ArrayBlockingQueue内部实现就是一个环形队列,它是一个定长队列,内部是用一个定长数组来实现的。另外著名的Disruptor开源Library也是用环形数组来实现的超高性能队列,具体原理不做解释,比较复杂。简单点说就是使用两个偏移量来标记数组的读位置和写位置,如果超过长度就折回到数组开头,前提是它们是定长数组。

10.ArrayList和LinkedList的区别?

ArrayList:

  • 基于动态数组的数据结构
  • 对于随机访问的get和set,ArrayList要优于LinkedList
  • 对于随机操作的add和remove,ArrayList不一定比LinkedList慢 (ArrayList底层由于是动态数组,因此并不是每次add和remove的时候都需要创建新数组)

LinkedList:

  • 基于双向链表的数据结构(双向链表遍历效率可能优于单向链表,因为双向链表可以在查找元素时,判断靠近头还是靠近尾,如果靠近头从头开始找,如果靠近尾从尾开始找)
  • 对于顺序操作,LinkedList不一定比ArrayList慢
  • 对于随机操作,LinkedList效率明显较低


#Java面试##面试题目#
全部评论
感谢参与【创作者计划2期·技术干货场】!欢迎更多牛油来写干货,瓜分总计20000元奖励!!技术干货场活动链接:https://www.nowcoder.com/link/czz2jsghtlq(参与奖马克杯将于每周五结算,敬请期待~)
点赞 回复 分享
发布于 2021-03-02 16:04
点赞 回复 分享
发布于 2021-03-09 21:43

相关推荐

10-30 10:16
南京大学 Java
龚至诚:给南大✌️跪了
点赞 评论 收藏
分享
11 30 评论
分享
牛客网
牛客企业服务