List常见面试题_五连问_非常详细解答_最后一题压轴题

🌍List常见基础面试题

1.说下Vector和ArrayList、LinkedList联系和区别?分别的使用场景

  • 线程安全
    • ArrayList:底层是数组实现,线程不安全,查询和修改非常快,但是增加和删除慢
    • LinkedList: 底层是双向链表,线程不安全,查询和修改速度慢,但是增加和删除速度快
    • Vector: 底层是数组实现,线程安全的,操作的时候使用synchronized进行加锁
  • 使用场景
    • Vector已经很少用了
    • 增加和删除场景多则用LinkedList
    • 查询和修改多则用ArrayList

2.如果需要保证线程安全,ArrayList应该怎么做,用有几种方式

方式一:自己写个包装类,根据业务一般是add/update/remove加锁 

方式二:Collections.synchronizedList(new ArrayList<>()); 使用synchronized加锁

方式三:CopyOnWriteArrayList<>()  使用ReentrantLock加锁

3. 如果回答到上面的点则继续问,了解CopyOnWriteArrayList吗?Collections.synchronizedList实现线程安全有什么区别, 使用场景是怎样的?

  • CopyOnWriteArrayList:执行修改操作时,会拷贝一份新的数组进行操作(add、set、remove等),代价十分昂贵,在执行完修改后将原来集合指向新的集合来完成修改操作,源码里面用ReentrantLock可重入锁来保证不会有多个线程同时拷贝一份数组

    • 场景:读高性能,适用读操作远远大于写操作的场景中使用(读的时候是不需要加锁的,直接获取,删除和增加是需要加锁的, 读多写少)

    • Collections.synchronizedList:线程安全的原因是因为它几乎在每个方法中都使用了synchronized同步锁

      • 场景:写操作性能比CopyOnWriteArrayList好,读操作性能并不如CopyOnWriteArrayList

4.CopyOnWriteArrayList的设计思想是怎样的,有什么缺点?

答案:设计思想:读写分离+最终一致

缺点:内存占用问题,写时复制机制,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象,如果对象大则容易发生Yong GC和Full GC

5.说下ArrayList的扩容机制是怎样的

注意:JDK1.7之前ArrayList默认大小是10,JDk1.7之后是0

未指定集合容量,默认是0,若已经指定大小则集合大小为指定的;
当集合第一次添加元素的时候,集合大小扩容为10
ArrayList的元素个数大于其容量,扩容的大小= 原始大小+原始大小/2

6.设计一个简单的ArrayList【需要包含 构造函数(有参和无参)、add(obj)、 扩容机制】

package com.cx330;

import java.io.Serializable;


public class MyArrayList implements Serializable {

    /**
     * 第一次扩容的容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于初始化空的List
     */
    private static final Object[] EMPTY_ELEMENT_DATA = {};

    /**
     * 实际存储的元素
     * transient:防止被序列化
     */
    transient Object[] elementData;

    /**
     * 实际List存储元素的长度
     */
    private int size;


    public MyArrayList() {

        this.elementData = EMPTY_ELEMENT_DATA;
    }

    public MyArrayList(int initCapacity) {

        if (initCapacity > 0) {
            this.elementData = new Object[initCapacity];
        } else if (initCapacity == 0) {
            this.elementData = EMPTY_ELEMENT_DATA;
        } else {
            throw new IllegalArgumentException("非法参数! 数组长度应该大于0");
        }
    }

    /**
     * 添加元素
     * @param e
     * @return
     */
    public boolean add(Object e) {

        //判断容量
        ensureCapacityInterval(this.size+1);

        //尾部插入
        this.elementData[size++]=e;

        return true;
    }

    /**
     * 检测是否扩容
     * @param minCapacity
     */
    private void ensureCapacityInterval(int minCapacity) {

        //初次扩容
        if (this.elementData==EMPTY_ELEMENT_DATA) {
            minCapacity=Math.max(DEFAULT_CAPACITY,minCapacity);
        }

        //需要的最容量大于当前数组的长度,则扩容
        if (this.elementData.length<minCapacity) {

            int oldCapacity=this.elementData.length;

            int newCapacity=oldCapacity+(oldCapacity>>>1);

            //扩容完之后新的容量还小于需要扩容的容量
            if (newCapacity-minCapacity<0) {
                newCapacity=minCapacity;
            }

            Object[] objects=new Object[newCapacity];
            System.arraycopy(this.elementData,0,objects,0,this.elementData.length);
            this.elementData=objects;
        }
    }

    /**
     * 通过下标获取对象
     * @param index
     * @return
     */
    public Object get(int index){

        //检查下表是否合法
        rangeCheck(index);

        return this.elementData[index];
    }

    private void rangeCheck(int index) {
        if (index>=this.size||index<0) {
            throw new IllegalArgumentException("非法下标");
        }
    }

    /**
     * 通过对象获取下标
     * @param e
     * @return
     */
    public int indexOf(Object e){

        if (e==null) {
            for (int i = 0; i < this.elementData.length; i++) {
                if (this.elementData[i]==null) {
                    return i;
                }
            }
        }else {
            for (int i = 0; i < this.elementData.length; i++) {
                if (e.equals(this.elementData[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 更新指定下标的元素
     * @param index
     * @param e
     * @return
     */
    public Object set(int index,Object e){

        rangeCheck(index);

        Object oldE=elementData[index];

        elementData[index]=e;

        return oldE;
    }

    /**
     * 删除指定下标的元素
     * @param index
     * @return
     */
    public Object remove(int index){

        rangeCheck(index);

        Object e=elementData[index];

        //判断删除元素的后面还有多少个元素
        int moveSize=size-index-1;

        if (moveSize>0) {
            System.arraycopy(elementData,index+1,elementData,index,moveSize);
        }

        //将多出的位置置为空,进而没有对象的引用,垃圾对象可以对其进行回收,如果不为空,将会有一个对象的引用,可能造成内存泄露
        elementData[--size]=null;

        return e;
    }

    /**
     * 获取数组实际大小
     * @return
     */
    public int size(){
        return this.size;
    }

}

补充:这一题如果要考虑并发修改这个集合的话,我们可以定义一个int类型的变量 modCount 在add,set,remove之前保存这个值,在进行写操作之后再次获取这个值,如果前后两次不相同,那么就说明存在多线程修改,我们就可以抛异常。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

C_x_330

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

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

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

打赏作者

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

抵扣说明:

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

余额充值