• 有趣的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)、 扩容机制】
    1. public class MyArrayList implements Serializable {
    2. //第一次扩容的容量 默认 10
    3. private static final int DEFAULT_CAPACITY = 10;
    4. //用于初始化空的list
    5. private static final Object[] EMPTY_ELEMENT_DATA = {};
    6. //实际存储的元素
    7. transient Object[] elementData;
    8. //实际list集合大小,从0开始
    9. private int size;
    10. public MyArrayList(){
    11. this.elementData = EMPTY_ELEMENT_DATA;
    12. }
    13. public MyArrayList(int initialCapcity){
    14. if(initialCapcity > 0){
    15. this.elementData = new Object[initialCapcity];
    16. } else if(initialCapcity == 0){
    17. this.elementData = EMPTY_ELEMENT_DATA;
    18. } else {
    19. throw new IllegalArgumentException("参数异常");
    20. }
    21. }
    22. public boolean add(Object e){
    23. //判断容量
    24. ensureCapacityInternal(size+1);
    25. this.elementData[size++] = e;
    26. return true;
    27. }
    28. //计算容量+确保容量
    29. private void ensureCapacityInternal(int minCapacity){
    30. //如果是初次扩容,则使用默认的容量
    31. if(elementData == EMPTY_ELEMENT_DATA){
    32. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    33. }
    34. if(minCapacity - elementData.length > 0){
    35. int oldCapacity = elementData.length;
    36. int newCapacity = oldCapacity + (oldCapacity >> 1);
    37. //如果新容量 < 最小容量, 则将最新的容量赋值给新的容量
    38. if(newCapacity - minCapacity < 0){
    39. newCapacity = minCapacity;
    40. }
    41. //创建新数组
    42. Object[] objects = new Object[newCapacity];
    43. //将旧的数组复制到新的数组里面
    44. System.arraycopy(elementData,0, objects,0,elementData.length);
    45. //修改引用
    46. elementData = objects;
    47. }
    48. }
    49. /**
    50. * 通过下标获取对象
    51. * @param index
    52. * @return
    53. */
    54. public Object get(int index){
    55. rangeCheck(index);
    56. return elementData[index];
    57. }
    58. private void rangeCheck(int index){
    59. if(index > size || size < 0){
    60. throw new IndexOutOfBoundsException("数组越界");
    61. }
    62. }
    63. /**
    64. * 判断对象所在的位置
    65. * @param o
    66. * @return
    67. */
    68. public int indexOf(Object o){
    69. if(o == null){
    70. for(int i=0; i < size; i++){
    71. if(elementData[i] == null){
    72. return i;
    73. }
    74. }
    75. }else {
    76. for(int i=0; i
    77. if(o.equals(elementData[i])){
    78. return i;
    79. }
    80. }
    81. }
    82. return -1;
    83. }
    84. public Object set(int index, Object obj){
    85. rangeCheck(index);
    86. Object oldValue = elementData[index];
    87. elementData[index] = obj;
    88. return oldValue;
    89. }
    90. /**
    91. * 根据索引删除元素
    92. * @param index
    93. * @return
    94. */
    95. public Object remove(int index){
    96. rangeCheck(index);
    97. Object oldValue = elementData[index];
    98. //计算要删除的位置后面有几个元素
    99. int numMoved = size - index -1;
    100. if(numMoved>0){
    101. System.arraycopy(elementData,index+1,elementData,index,numMoved);
    102. }
    103. //将多出的位置为空,没有引用对象,垃圾收集器可以回收,如果不为空,将会保存一个引用,可能会造成内存泄露
    104. elementData[--size] = null;
    105. return oldValue;
    106. }
    107. //获取数组实际大小
    108. public int size(){
    109. return this.size;
    110. }
    111. }

    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

  • 相关阅读:
    CSRF +self xss的运用【DVWA测试】
    Springcloud----sleuth+zipkin链路追踪
    AWS SAP-C02教程6--安全
    A133P EC200M模块调试
    闭包(C#)
    软件工程毕业设计课题(50)微信小程序毕业设计JAVA校园浴室预约小程序系统设计与实现
    递推算法及解题套路
    鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    软件开发全文档整理(原件获取)
    电脑自动关机是什么原因?解决方案全解析!
  • 原文地址:https://blog.csdn.net/Swing_yue/article/details/126710031