大厂高频面试题:谈谈你对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-09 09:39
门头沟学院 C++
HHHHaos:这也太虚了,工资就一半是真的
点赞 评论 收藏
分享
Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
评论
11
30
分享
牛客网
牛客企业服务