纯干货:Java集合总结

104 阅读12分钟

1.概述

image.png   Java 集合框架包括 Collection 和 Map 两个接口,其中 Collection 接口是 ListSetQueue 接口的父接口,Map 接口是与 Collection 平行的一个接口。常用的 Java 集合类如下:

  • Iterator是一个迭代器接口,它提供了迭代集合元素的方法。它是所有集合类都实现的接口,通过 Iterator接口,可以实现对集合元素的遍历。
  • List: 存储的元素是有序且可重复的
  • Set:存储的元素是无序且不可重复的
  • Queue:按特定规则来实现来确定先后顺序,存储的元素是有序而且可重复的,
  • Map: 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
  • CollectionsArrays 是工具类,提供了操作集合和数组的静态方法。

2. 底层集合框架

2.1 List

  Java中的List是一种基于序列的集合,它允许用户在集合中插入删除访问元素。在Java集合框架中,List接口是一个基本的接口,它定义了一些常用的方法,例如添加元素、获取元素、删除元素等等。下面我们来总结一下Java集合框架中的List: image.png

  • ArrayList是Java集合框架中最常用的List实现类之一,它是一个基于数组的实现,可以动态增加和减少元素。与数组相比,它具有更强大的操作和更高效的性能。
  • LinkedList是另一个常用的List实现类,它是基于链表的实现,与ArrayList相比,它对于插入和删除元素的效率更高
  • Vector是Java集合框架中最早的List实现类之一,它与ArrayList非常相似,但是Vector是线程安全的,因此它通常用于多线程环境中
  • Stack是基于Vector实现的一个栈,它提供了push()pop()等方法,可以方便地进行入栈和出栈操作

ArraylistVector 的区别:

  • ArrayListList 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
  • Vector 是 List 的古老实现类,底层使用Object[ ] 存储,线程安全的

Arraylist与LinkedList 区别:

  • 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全

  • 底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别)

  • 插入和删除是否受元素位置的影响:

    • ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作
    • LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响(add(E e)addFirst(E e)addLast(E e)removeFirst()removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element)remove(Object o)) 时间复杂度近似为 O(n) ,因为需要先移动到指定位置再插入。
  • 内存空间占用: ArrayList的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。

ArrayList扩容机制:最小长度为10,每次添加元素的时候,都会调用ensureCapacityInternal方法,来确定是否要执行扩容,扩容的核心逻辑在grow方法:

  • 算出新数组的长度:当前数组的1.5倍(oldCapacity + (oldCapacity >> 1))
  • 创建一个新数组,将原数据拷贝到新数组中。

2.2 Set

  Set 是 Java 集合框架中的一部分,它表示一组唯一的元素,除了Set之外,Java 还提供了一些扩展的 Set 接口和实现类,如 Sorted:Set 接口、NavigableSet 接口,在本篇文章中,我将为你总结 Java 集合 Set 的体系结构 image.png

  • HashSet 是基于哈希表实现的 Set 集合,它不保证元素的顺序,可以存储 null 值。
    • 哈希表是一种使用哈希函数进行快速查找的数据结构。在哈希表中,每个元素都有一个唯一的键值,该键值通过哈希函数计算得出,用于在数据结构中查找该元素
    • HashSet 的实现方式是:将元素插入哈希表中,使用元素的 hashCode() 方法计算哈希值,然后通过哈希值找到元素在哈希表中的位置。如果该位置已经被占用,则使用链表或红黑树等数据结构来解决冲突
  • TreeSet 是基于红黑树实现的 Set 集合,它可以对元素进行排序,不能存储 null 值。
    • 红黑树是一种自平衡的二叉查找树,它保证了在最坏情况下的时间复杂度为 O(log n)。在红黑树中,每个节点都有一个颜色属性,可以是红色或黑色,根据颜色和其他规则来维护平衡。
    • TreeSet 的实现方式是:将元素插入红黑树中,每个节点都代表一个元素,通过比较节点之间的值来确定元素的顺序
  • LinkedHashSet 是基于哈希表和链表实现的 Set 集合,它保证了元素的顺序和插入顺序一致,可以存储 null 值
    • LinkedHashSet 的实现方式是:将元素插入哈希表中,同时在元素和哈希表之间维护一个双向链表,用于保证元素的顺序
  • ConcurrentSkipListSet 类,它是线程安全的,支持并发访问,使用跳表(Skip List)实现,具有高效的插入、删除和查找操作

SortedSet 接口是 Set 接口的子接口,它扩展了 Set 接口,提供了根据元素的排序顺序访问集合中元素的方法,SortedSet 接口中定义了一些额外的方法,如:

  • first():返回集合中的第一个元素。
  • last():返回集合中的最后一个元素。
  • subSet(fromElement, toElement):返回集合中介于 fromElement(包括)和 toElement(不包括)之间的元素的视图。
  • headSet(toElement):返回集合中小于 toElement 的元素的视图。
  • tailSet(fromElement):返回集合中大于等于 fromElement 的元素的视图。

  SortedSet 接口的实现类有 TreeSet,它使用红黑树实现,支持快速查找和排序

NavigableSet 接口是 SortedSet 接口的子接口,它在 SortedSet 的基础上提供了一些额外的导航方法,如:

  • lower(e):返回集合中小于 e 的最大元素,如果不存在则返回 null。
  • floor(e):返回集合中小于等于 e 的最大元素,如果不存在则返回 null。
  • ceiling(e):返回集合中大于等于 e 的最小元素,如果不存在则返回 null。
  • higher(e):返回集合中大于 e 的最小元素,如果不存在则返回 null。
  • pollFirst():移除并返回集合中的第一个元素。
  • pollLast():移除并返回集合中的最后一个元素。

2.3 Map

  Map 是一种基于键值对存储的数据结构,它允许将一个键(key)映射到一个值(value),并提供了快速查找和访问值的方法。Java 中的 Map 接口是一个由多个实现类组成的体系结构,这些实现类分别具有不同的特点和适用场景。下面是Java Map 体系结构的总结:

image.png

  • SortedMap接口:继承自Map接口,它定义了一组有序的操作,用于对Map中的键进行排序

  • NavigableMap接口:继承自SortedMap接口,它定义了一组用于导航(如查找最近的键、获取前驱和后继键等)的方法

  • ConcurrentMap接口:是Map接口的一个子接口,它定义了一组支持并发访问的操作

  • Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的。

    • 对于HashTable,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换
  • HashMapJava 中最常用的 Map 实现之一,它使用哈希表来存储键值对,提供了 O(1) 的时间复杂度来查找和访问元素。HashMap 的内部实现涉及到哈希函数、哈希冲突解决方法、扩容机制等方面

  • WeakHashMap 是一种基于弱引用的 Map 实现,它的特点是当某个键不再被引用时,相应的值将自动被回收。WeakHashMap 的内部实现利用了 Java 中的 WeakReference 弱引用机制

  • TreeMap 是一种基于红黑树实现的有序 Map,它提供了快速的查找和遍历元素的方式。TreeMap 内部使用红黑树来维护元素的顺序,它的排序方式可以通过自定义比较器来指定

    • 在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常
  • LinkedHashMap 是一种保持插入顺序的 Map,它提供了与 HashMap 相同的查找和访问效率,同时保证了元素的顺序。

    • LinkedHashMap 内部使用双向链表来维护元素的插入顺序,它的访问顺序可以通过指定构造函数中的 accessOrder 参数来决定
  • ConcurrentHashMap 是一种线程安全的 Map 实现,它提供了高效的并发访问和修改操作。ConcurrentHashMap 内部使用分段锁来实现线程安全,同时也允许多个线程同时对不同的段进行操作,从而提高了并发性能

3. 最佳实践

3.1 list

  • 根据实际需要选择不同的实现类。如果需要经常进行插入和删除操作,可以选择 LinkedList,如果需要快速随机访问,可以选择 ArrayList
  • 在使用 Vector 时,需要注意同步开销会影响性能,如果不需要线程安全,可以使用 ArrayListLinkedList
  • 在使用 subList() 方法时,需要注意返回的子列表是原列表的一个视图,对其进行修改会影响原列表
  • 在创建 List 对象时,应该根据需要预估列表的大小,以避免不必要的扩容操作
  • 避免在循环中使用 remove() 方法,因为它会改变列表的结构,可能会导致索引错乱和异常。如果需要删除元素,可以使用迭代器的 remove() 方法

3.2 Set

  • 在创建 Set 对象时,可以通过指定容量来提高性能。当 Set 中的元素数量达到容量时,Set 会自动扩容。为了避免频繁的扩容,可以指定一个适当的容量
  • 实现 equalshashCode 方法:如果你打算将自定义对象存储在 Set 中,则必须实现 equalshashCode 方法。这是因为 Set 会使用这两个方法来确定两个对象是否相等。如果没有正确地实现这两个方法,则 Set 可能无法正确地工作
  • 避免 null 值:Java Set 不允许存储重复的元素,因此如果你尝试将 null 值添加到 Set 中,则可能会出现问题。因此,最好避免将 null 值添加到 Set
  • 使用迭代器可以遍历 Set 中的元素。在使用迭代器时,要确保在迭代过程中不要修改 Set 中的元素
  • Java Set 有多个实现类,如 HashSetLinkedHashSetTreeSet 等。选择正确的实现类是关键。如果你需要存储不需要保持顺序的元素,则使用 HashSet;如果你需要保留插入顺序,则使用 LinkedHashSet;如果你需要对元素进行排序,则使用 TreeSet

3.3 map

  • Java Map 不是线程安全的,因此在多线程环境中使用时要小心。如果多个线程同时修改同一个 Map 对象,则可能会出现并发修改异常。为了避免这种情况,可以使用 ConcurrentHashMapCollections.synchronizedMap() 方法创建线程安全的 Map 对象
  • 使用 entrySet() 方法遍历 Map:使用 entrySet() 方法可以遍历 Map 中的键值对,而不是只遍历键或值。这样可以避免在遍历 Map 时需要再次查找值
  • 在创建 Map 对象时,可以通过指定容量来提高性能。当 Map 中的键值对数量达到容量时,Map 会自动扩容。为了避免频繁的扩容,可以指定一个适当的容量
  • 如果你需要在多线程环境中使用 Map 对象,则可以使用 ConcurrentHashMap。这是一个线程安全的 Map 实现,可以提高代码的性能和可靠性
  • 如果你需要在多线程环境中使用 Map 对象,则可以使用 ConcurrentHashMap。这是一个线程安全的 Map 实现,可以提高代码的性能和可靠性
  • 如果你需要按照插入顺序遍历 Map,则可以使用 LinkedHashMap 实现。LinkedHashMap 可以记住插入顺序,并根据插入顺序进行遍历

本文只是对 Java 集合体系结构进行了简要的介绍和总结,希望本文能够为开发者提供一些参考和帮助,让大家能够更加轻松和高效地使用 Java 集合,总结不易,感兴趣的可以点赞加收藏;后续,也会对常见集合的源码做深入分析,点赞关注不迷路哦。