ArrayList常见面试题

1、ArrayList是什么?可以用来干嘛?

ArrayList就是有序的动态数组列表,主要⽤来装载数据,只能装载包装类(Integer,String,Double等),它的主要底层实现是数组Object[]
elementData

2、ArrayList与LinkedList的区别?

1、ArrayList的查找和访问元素的速度较快,但新增,删除的速度较慢,LinkedList的查找和访问元素的速度较慢,但是他的新增,删除的速度较快
2、ArrayList需要一份连续的内存空间,LinkedList不需要连续的内存空间(特别地,当创建一个ArrayList集合的时候,连续的内存空间必须要大于等于创建的容量)
3、两者都是线程不安全的

3、为啥线程不安全还使用他呢?

因为在我们正常得使用场景中,都是用来查询的,不会涉及太频繁的增删,如果涉及到频繁的增删,可以使用LinkedList,如果需要线程安全的就是可以使用Vector,CopyOrWriteArrayList

4、它的底层实现是数组,但是数组的⼤⼩是定⻓的,如果我们不断的往⾥⾯添加数据的话,不会有问题吗?

JDK1.7–》相当于设计模式中的饿汉式 第一次创建无参构造器时就创建一个初始容量为10的数组 JDK1.8
–》相当于涉及模式中的懒汉式 ArrayList可以通过构造⽅法在初始化的时候指定底层数组的⼤⼩。 通过⽆参构造⽅法的⽅式ArrayList()初始化,则赋值底层数Object[] elementData为⼀个默认空数组 Object[]
DEFAULTCAPACITY_EMPTY_ELEMENTDATA =
{}所以数组容量为0,只有真正对数据进⾏添加add时,才分配默认DEFAULT_CAPACITY = 10的初始容量。 private
static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//无参数
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//有参数
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

5、ArrayList数组扩容

假设我们有一个长度为10的数组,此时需要新增一个,发现满了,ArrayList会进行扩容
在这里插入图片描述

1、重新定义一个10*1.5的数组
在这里插入图片描述

然后把原数组的数据,原封不动的复制到新数组中,然后把ArrauList的地址指向新数组
在这里插入图片描述

//详细扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

6、1.7和1.8版本初始化的区别

1.7的时候是初始化就创建一个容量为10的数组,1.8后是初始化先创建一个空数组,第一次add时才扩容为10

7、ArrayList在增删的时候是怎么做的?(为什么慢)

新增
ArrayList有指定index(索引下标)新增,也有尾部新增,但是都有校验长度的判断ensureCapacityInternal,就是说如果⻓度不够,是需要扩容的。
在扩容的时候,1.7是取余,1.8是位运算,右移⼀位,其实就是除以2这个操作。

//尾插add方法
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
//index(索引)插入,指定位置新增的时候,在校验之后的操作很简单,就是数组的copy
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
//真正扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
//删除
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

总结:总的来说,不论是删除还是新增,其实本质上都是移动位置,当指定位置新增的时候,新增的索空出,后面向后移一位,然后赋值,当删除时,也是一样,将要删除的索引下标的值置为null。

8、ArrayList(int initialCapacity)会不会初始化数组⼤⼩?

会初始化大小,但是list的大爱小没有变,也就是size不变
⽽且将构造函数与initialCapacity结合使⽤,然后使⽤set()会抛出异常,尽管该数组已创建,但是⼤⼩设置不正确。
使⽤sureCapacity()也不起作⽤,因为它基于elementData数组⽽不是⼤⼩。
还有其他副作⽤,这是因为带有sureCapacity()的静态DEFAULT_CAPACITY。
进⾏此⼯作的唯⼀⽅法是在使⽤构造函数后,根据需要使⽤add()多次。

在这里插入图片描述

9、ArrayList是线程安全的么?

不是,可以用Vector,Collections.synchronizedList(),原理都是給方法套个synchronized,
CopyOnWriteArrayList

10、ArrayList⽤来做队列合适么?

队列⼀般是FIFO(先⼊先出)的,如果⽤ArrayList做队列,就需要在数组尾部追加数据,数组头部删除
数组,反过来也可以。但是⽆论如何总会有⼀个操作会涉及到数组的数据搬迁,这个是⽐较耗费性能的。
结论:ArrayList不适合做队列。

11、那数组适合⽤来做队列么?

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

12、ArrayList的遍历和LinkedList遍历性能⽐较如何?

ArrayList要比LinkedList快得多,ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。

13、ArrayList常⽤的⽅法总结

boolean add(E e)
将指定的元素添加到此列表的尾部。 void add(int index, E element)
将指定的元素插⼊此列表中的指定位置。 boolean addAll(Collection<? extends E> c)
按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。 boolean addAll(int index, Collection<? extends E> c)
从指定的位置开始,将指定 collection 中的所有元素插⼊到此列表中。 void clear()
移除此列表中的所有元素。 Object clone()
返回此 ArrayList 实例的浅表副本。 boolean contains(Object o)
如果此列表中包含指定的元素,则返回 true。 void ensureCapacity(int minCapacity)
如有必要,增加此 ArrayList 实例的容量,以确保它⾄少能够容纳最⼩容量参数所指定的元素数。 E get(int index)
返回此列表中指定位置上的元素。 int indexOf(Object o)
返回此列表中⾸次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1。 boolean isEmpty()
如果此列表中没有元素,则返回 true int lastIndexOf(Object o)
返回此列表中最后⼀次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。 E remove(int index)
移除此列表中指定位置上的元素。 boolean remove(Object o)
移除此列表中⾸次出现的指定元素(如果存在)。 protected void removeRange(int fromIndex, int toIndex)
移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。 E set(int index, E element)
⽤指定的元素替代此列表中指定位置上的元素。 int size()
返回此列表中的元素数。 Object[] toArray()
按适当顺序(从第⼀个到最后⼀个元素)返回包含此列表中所有元素的数组。 T[] toArray(T[] a)
按适当顺序(从第⼀个到最后⼀个元素)返回包含此列表中所有元素的数组;返回数组的运⾏时类型是指定数组的运⾏时类型。 void trimToSize()
将此 ArrayList 实例的容量调整为列表的当前⼤⼩。

声明:本文是参考敖丙大佬的推文下写的,若有侵权,请私聊我

我是小白弟弟,一个在互联网行业的小白,立志成为一名架构师
https://blog.csdn.net/zhouhengzhe?t=1

  • 9
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhz小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值