Java List 集合

75 阅读3分钟

ArrayList

  • ArrayList 是基于动态数组的数据结构,线程不安全。
  • 随机访问性能 ArrayList 优于 LinkedList。ArrayList 时间复杂度为 O(1) ,LinkedList 时间复杂度为 O(n)【需要遍历整个链表才能找到指定的元素】

LinkedList

image.png

  • 基于链表的数据结构,线程不安全。
  • 插入和删除性能 LinkedList 优于 ArrayList。 LinkedList 时间复杂度为 O(1),ArrayList 的时间复杂度为 O(n)

Vector

和 ArrayList 一样都是基于数组实现,但线程安全【通过 synchronized 关键字保证】。

  • Vector 性能比 ArrayList 差,单线程下 ArrayList 要比 Vector 快
  • 扩容时,ArrayList 扩容50%,Vector 扩容 100%

image.png

CopyOnWriteArrayList

  • 大多数应用场景下读操作的比例远远大于写,所以读的时候没必要加锁,写写场景下加锁互斥即可

  • CopyOnWriteArrayList【写时复制容器】满足上述场景,即:读写不互斥。写入操作时,进行一次自我复制产生一个副本,写操作就在副本中执行,写完之后,再将副本替换原来的数据。这样,在写数据的同时不影响读数据操作。

  • 读操作源码

    • 从数组中获取指定下标的数据,读不需要加锁,所以只是简单的不加锁方法
    • image.png
  • 写操作

    • image.png
    • image.png
    • 执行写操作时,先进行 lock 加锁。然后复制原数组创建一个长度加一的新数组【副本数组】,新增操作基于副本数组操作。新增操作完成后,使用副本数组替换旧数组。array 是 volatile 的,保证了多线程之间的可见性。
  • 优势

    • 读写不互斥、写写互斥,多线程并发安全。读写分离,提高并发吞吐

    • 不支持fail-first机制,不抛出 ConcurrentModificationException 异常

      当我们使用 iterator 进行遍历时,如果遍历途中使用集合的 add/remove 方法进行修改,此时会抛出 ConcurrentModificationException 异常,产生 fail-fast 事件

      CopyOnWriteArrayList 在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 ConcurrentModificationException

  • 劣势

    • 只保证最终一执性。一条线程在执行修改操作,另一条线程在执行读取操作,读取的线程并不能看到最新的数据。

      即使执行 setArray() 将指向改成了新数组,原本读取的线程也不能看到最新的数据。因为读取线程在执行读操作时并不是直接访问成员array完成的,而是通过getArray()方法的形式获取到的数组数据,在getArray()方法执行完成之后,读取数据的线程拿到的引用已经是旧数组的地址了,之后就算修改成员array的指向也不会影响get的访问

    • 数据过大时,内存占用高