List集合面试题

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

  • ArrayList:底层数组实现,线程不安全,查询和修改速度快,但是增加和删除慢

  • LinkedList:底层是双向链表,线程不安全,查询和修改速度慢,但是增加和删除快

  • Vector:底层是数组实现,线程安全,操作的时候使用synchronized进行加锁

使用场景

  • Vector已经很少用了

  • 增加和删除场景多使用LinkedList

  • 查询和修改多使用ArrayList

2.如果需要保证线程安全,ArrayList应该怎么做

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

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

  • 方式三:new CopyOnWriteArrayList();使用ReentrantLock加锁

3.CopyOnWriteArrayList和Collections.synchronizedList实现线程安全区别和使用场景

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

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

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

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

4.CopyOnWriteArrayList的设计思想,有什么缺点

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

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

5.说下ArrayList的扩容机制

注意:JDK7之前ArrayList默认大小是10,JDK7之后默认大小0

未指定集合容量,默认是0,若已经指定大小则集合大小为指定的;

当集合第一次添加元素的时候,集合大小扩容为10

ArrayList的元素个数大于其容量,扩容大小=原始大小+原始大小/2

6.编写简单的ArrayList

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 Object[] elementData;

    // 实际集合的大小,0开始
    private int size;

    public MyArrayList() {
        this.elementData = EMPTY_ELEMENT_DATA;
    }

    public MyArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENT_DATA;
        } else {
            throw new IllegalArgumentException("参数异常");
        }
    }


    /**
     * 添加元素
     *
     * @param e
     * @return boolean
     * @author Gen
     * @date 2023/3/14
     */
    public boolean add(Object e) {
        // 判断容量是否需要扩容
        grow(size + 1);
        // 数组末尾插入元素
        elementData[size++] = e;
        return true;
    }

    /**
     * 扩容
     *
     * @param minCapacity 所需容量
     * @return void
     * @author Gen
     * @date 2023/3/14
     */
    private void grow(int minCapacity) {
        // 初次扩容,使用默认的容量
        if (elementData == EMPTY_ELEMENT_DATA) {
            minCapacity = Math.max(minCapacity, DEFAULT_CAPACITY);
        }

        // 所需容量大于数组长度则要扩容
        if (minCapacity > elementData.length) {
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);

            // 如果新容量<最小容量,则新容量=最小容量
            if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
            }

            // 数组拷贝
            Object[] objects = new Object[newCapacity];
            System.arraycopy(elementData, 0, objects, 0, elementData.length);
            elementData = objects;
        }
    }

    /**
     * 通过索引获取对象
     *
     * @param index
     * @return java.lang.Object
     * @author Gen
     * @date 2023/3/14
     */
    public Object get(int index) {
        rangeCheck(index);
        return elementData[index];
    }

    /**
     * 检查下标传参
     *
     * @param index
     * @return void
     * @author Gen
     * @date 2023/3/14
     */
    private void rangeCheck(int index) {
        if (index >= size || size < 0) {
            throw new IndexOutOfBoundsException("数组下标越界");
        }
    }

    /**
     * 判断对象所在下标位置
     *
     * @param o
     * @return int
     * @author Gen
     * @date 2023/3/14
     */
    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;
    }

    /**
     * 根据下标修改元素,并返回旧的元素
     *
     * @param index
     * @param obj
     * @return java.lang.Object
     * @author Gen
     * @date 2023/3/14
     */
    public Object set(int index, Object obj) {
        rangeCheck(index);

        Object oldValue = elementData[index];
        elementData[index] = obj;
        return oldValue;
    }

    /**
     * 根据下标移除元素,并返回旧的元素
     *
     * @param index
     * @return java.lang.Object
     * @author Gen
     * @date 2023/3/14
     */
    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;
    }

    /**
     * 返回数组大小
     *
     * @param
     * @return int
     * @author Gen
     * @date 2023/3/14
     */
    public int size() {
        return this.size;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java集合面试题52共有52个问题,具体问题如下: 1. Java集合框架的核心接口是什么? 2. ArrayList和LinkedList的区别是什么? 3. HashSet和TreeSet的区别是什么? 4. HashMap和Hashtable的区别是什么? 5. ConcurrentHashMap和Hashtable的区别是什么? 6. 如何实现一个线程安全集合? 7. 如何遍历一个ArrayList? 8. 如何遍历一个LinkedList? 9. 如何遍历一个HashSet? 10. 如何遍历一个TreeSet? 11. 如何遍历一个HashMap的Key? 12. 如何遍历一个HashMap的Value? 13. 如何遍历一个HashMap的Entry? 14. 如何遍历一个Hashtable的Key? 15. 如何遍历一个Hashtable的Value? 16. 如何遍历一个Hashtable的Entry? 17. 如何遍历一个ConcurrentHashMap的Key? 18. 如何遍历一个ConcurrentHashMap的Value? 19. 如何遍历一个ConcurrentHashMap的Entry? 20. 如何使用Collections类对List进行排序? 21. 如何使用Collections类对Set进行排序? 22. 如何使用Collections类对Map的Key进行排序? 23. 如何使用Collections类对Map的Value进行排序? 24. 如何使用Comparator接口对对象进行排序? 25. 如何使用Comparable接口对对象进行排序? 26. 如何使用Iterator遍历集合? 27. 迭代器的remove()方法和集合的remove()方法有什么区别? 28. 什么是Fail-Fast机制? 29. 什么是Fail-Safe机制? 30. 如何使用ListIterator进行双向遍历? 31. 如何使用Enumeration进行遍历? 32. 如何使用Iterator进行并发修改的安全遍历? 33. 如何使用并发集合类进行并发操作? 34. 如何使用CopyOnWriteArrayList进行并发操作? 35. 如何使用CopyOnWriteArraySet进行并发操作? 36. 如何使用ConcurrentSkipListSet进行并发操作? 37. 如何使用BlockingQueue进行并发操作? 38. 如何使用LinkedBlockingQueue进行并发操作? 39. 如何使用ArrayBlockingQueue进行并发操作? 40. 如何使用PriorityBlockingQueue进行并发操作? 41. 如何使用SynchronousQueue进行并发操作? 42. 如何使用ConcurrentLinkedQueue进行并发操作? 43. 如何使用DelayQueue进行并发操作? 44. 如何使用ConcurrentHashMap进行并发操作? 45. 如何使用ConcurrentSkipListMap进行并发操作? 46. 如何使用CountDownLatch进行并发操作? 47. 如何使用CyclicBarrier进行并发操作? 48. 如何使用Semaphore进行并发操作? 49. 如何使用Exchanger进行并发操作? 50. 如何使用Lock和Condition进行并发操作? 51. 如何使用ReadWriteLock进行并发操作? 52. 如何使用AtomicInteger进行并发操作? 相关问题: 1. Java基础知识面试题有哪些? 2. Java多线程面试题有哪些? 3. Java异常处理面试题有哪些?

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

水宝的滚动歌词

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

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

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

打赏作者

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

抵扣说明:

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

余额充值