有趣的java面试题-集合篇 (一) List

一、Vector和ArrayList、LinkedList联系和区别?分别的使用场景

答案:

线程安全

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

使用场景

        Vector已经很少用了
        增加和删除场景多则用LinkedList
        查询和修改多则用ArrayList

二、基于List进行继续追问,连环炮

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

方式一:自己写个包装类,根据业务一般是add/update/remove加锁 
方式二:Collections.synchronizedList(new ArrayList<>()); 使用synchronized加锁
方式三:CopyOnWriteArrayList<>()  使用ReentrantLock加锁
 

三、基于List追问,追杀篇

  • 了解CopyOnWriteArrayList吗?和 Collections.synchronizedList实现线程安全有什么区别, 使用场景是怎样的?

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

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

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

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

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

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

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

四、基于List进行继续追问-暗器篇

     问:说下ArrayList的扩容机制是怎样的

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

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

      问: 为什么是1.5倍?

其计算的时候是利用 >> 位移1位的方式把原本的值减半,这种方式是CPU操作的方式性能很快。至于为什么是 位移一位,或许是1.5倍是一个比较合适的扩容大小。

五、基于list继续追问-大招篇

     手写ArrayList

  • 设计一个简单的ArrayList【需要包含 构造函数(有参和无参)、add(obj)、 扩容机制】
public class MyArrayList implements Serializable {
    
    //第一次扩容的容量 默认 10
    private static final int DEFAULT_CAPACITY = 10;
    //用于初始化空的list
    private static final Object[] EMPTY_ELEMENT_DATA = {};
    //实际存储的元素
    transient Object[] elementData;
    //实际list集合大小,从0开始
    private int size;
    
    public MyArrayList(){
        this.elementData = EMPTY_ELEMENT_DATA;
    }
    
    public MyArrayList(int initialCapcity){
        if(initialCapcity > 0){
            this.elementData = new Object[initialCapcity];
        } else if(initialCapcity == 0){
            this.elementData = EMPTY_ELEMENT_DATA;
        } else {
            throw new IllegalArgumentException("参数异常");
        }
    }
    
    public boolean add(Object e){
        //判断容量
        ensureCapacityInternal(size+1);
        this.elementData[size++] = e;
        return true;
    }
    
    //计算容量+确保容量
    private void ensureCapacityInternal(int minCapacity){
        //如果是初次扩容,则使用默认的容量
        if(elementData == EMPTY_ELEMENT_DATA){
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        if(minCapacity - elementData.length > 0){
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //如果新容量 < 最小容量, 则将最新的容量赋值给新的容量
            if(newCapacity - minCapacity < 0){
                newCapacity = minCapacity;
            }
            //创建新数组
            Object[] objects = new Object[newCapacity];
            //将旧的数组复制到新的数组里面
            System.arraycopy(elementData,0, objects,0,elementData.length);
            //修改引用
            elementData = objects;
        }
    }

    /**
     * 通过下标获取对象
     * @param index
     * @return
     */
    public Object get(int index){
        rangeCheck(index);
        return elementData[index];
    }
    
    private void rangeCheck(int index){
        if(index > size || size < 0){
            throw  new IndexOutOfBoundsException("数组越界");
        }
    }
    
    /**
     * 判断对象所在的位置
     * @param o
     * @return
     */
    public int indexOf(Object o){
        if(o == null){
            for(int i=0; i < size; i++){
                if(elementData[i] == null){
                    return i;
                }
            }
        }else {
            for(int i=0; i<size; i++){
                if(o.equals(elementData[i])){
                    return i;
                }
            }
        }
        return -1;
    }
    
    public Object set(int index, Object obj){
        rangeCheck(index);
        Object oldValue = elementData[index];
        elementData[index] = obj;
        return oldValue;
    }
    
    /**
     * 根据索引删除元素
     * @param index
     * @return
     */
    public Object remove(int index){
        rangeCheck(index);
        Object oldValue = elementData[index];
        //计算要删除的位置后面有几个元素
        int numMoved = size - index -1;
        if(numMoved>0){
            System.arraycopy(elementData,index+1,elementData,index,numMoved);
        }
        //将多出的位置为空,没有引用对象,垃圾收集器可以回收,如果不为空,将会保存一个引用,可能会造成内存泄露
        elementData[--size] = null;
        return oldValue;
    }
    
    //获取数组实际大小
    public int size(){
        return this.size;
    }
    
}

System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length)参数介绍

Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length  : 要copy的数组的长度

The end

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值