1.概述
Java 集合框架包括
Collection 和 Map 两个接口,其中 Collection 接口是 List、Set 和 Queue 接口的父接口,Map 接口是与 Collection 平行的一个接口。常用的 Java 集合类如下:
Iterator是一个迭代器接口,它提供了迭代集合元素的方法。它是所有集合类都实现的接口,通过Iterator接口,可以实现对集合元素的遍历。List: 存储的元素是有序且可重复的Set:存储的元素是无序且不可重复的Queue:按特定规则来实现来确定先后顺序,存储的元素是有序而且可重复的,Map: 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。Collections和Arrays是工具类,提供了操作集合和数组的静态方法。
2. 底层集合框架
2.1 List
Java中的List是一种基于序列的集合,它允许用户在集合中插入、删除和访问元素。在Java集合框架中,List接口是一个基本的接口,它定义了一些常用的方法,例如添加元素、获取元素、删除元素等等。下面我们来总结一下Java集合框架中的List:
ArrayList是Java集合框架中最常用的List实现类之一,它是一个基于数组的实现,可以动态增加和减少元素。与数组相比,它具有更强大的操作和更高效的性能。LinkedList是另一个常用的List实现类,它是基于链表的实现,与ArrayList相比,它对于插入和删除元素的效率更高Vector是Java集合框架中最早的List实现类之一,它与ArrayList非常相似,但是Vector是线程安全的,因此它通常用于多线程环境中Stack是基于Vector实现的一个栈,它提供了push()和pop()等方法,可以方便地进行入栈和出栈操作
Arraylist 和 Vector 的区别:
ArrayList是List的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;- Vector 是 List 的古老实现类,底层使用Object[ ] 存储,线程安全的
Arraylist与与LinkedList 区别:
-
是否保证线程安全:
ArrayList和LinkedList都是不同步的,也就是不保证线程安全 -
底层数据结构:
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 的体系结构
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 体系结构的总结:
-
SortedMap接口:继承自Map接口,它定义了一组有序的操作,用于对Map中的键进行排序 -
NavigableMap接口:继承自SortedMap接口,它定义了一组用于导航(如查找最近的键、获取前驱和后继键等)的方法 -
ConcurrentMap接口:是Map接口的一个子接口,它定义了一组支持并发访问的操作 -
Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的。- 对于
HashTable,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换
- 对于
-
HashMap是Java中最常用的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时,需要注意同步开销会影响性能,如果不需要线程安全,可以使用ArrayList或LinkedList - 在使用
subList()方法时,需要注意返回的子列表是原列表的一个视图,对其进行修改会影响原列表 - 在创建
List对象时,应该根据需要预估列表的大小,以避免不必要的扩容操作 - 避免在循环中使用
remove()方法,因为它会改变列表的结构,可能会导致索引错乱和异常。如果需要删除元素,可以使用迭代器的remove()方法
3.2 Set
- 在创建
Set对象时,可以通过指定容量来提高性能。当Set中的元素数量达到容量时,Set 会自动扩容。为了避免频繁的扩容,可以指定一个适当的容量 - 实现
equals和hashCode方法:如果你打算将自定义对象存储在Set中,则必须实现equals和hashCode方法。这是因为Set会使用这两个方法来确定两个对象是否相等。如果没有正确地实现这两个方法,则Set可能无法正确地工作 - 避免
null值:Java Set不允许存储重复的元素,因此如果你尝试将null值添加到Set中,则可能会出现问题。因此,最好避免将null值添加到Set中 - 使用迭代器可以遍历
Set中的元素。在使用迭代器时,要确保在迭代过程中不要修改Set中的元素 Java Set有多个实现类,如HashSet、LinkedHashSet、TreeSet等。选择正确的实现类是关键。如果你需要存储不需要保持顺序的元素,则使用HashSet;如果你需要保留插入顺序,则使用LinkedHashSet;如果你需要对元素进行排序,则使用TreeSet
3.3 map
Java Map不是线程安全的,因此在多线程环境中使用时要小心。如果多个线程同时修改同一个Map对象,则可能会出现并发修改异常。为了避免这种情况,可以使用ConcurrentHashMap或Collections.synchronizedMap()方法创建线程安全的Map对象- 使用
entrySet()方法遍历Map:使用entrySet()方法可以遍历Map中的键值对,而不是只遍历键或值。这样可以避免在遍历Map时需要再次查找值 - 在创建
Map对象时,可以通过指定容量来提高性能。当Map中的键值对数量达到容量时,Map会自动扩容。为了避免频繁的扩容,可以指定一个适当的容量 - 如果你需要在多线程环境中使用
Map对象,则可以使用ConcurrentHashMap。这是一个线程安全的Map实现,可以提高代码的性能和可靠性 - 如果你需要在多线程环境中使用 Map 对象,则可以使用
ConcurrentHashMap。这是一个线程安全的Map实现,可以提高代码的性能和可靠性 - 如果你需要按照插入顺序遍历
Map,则可以使用LinkedHashMap实现。LinkedHashMap可以记住插入顺序,并根据插入顺序进行遍历
本文只是对 Java 集合体系结构进行了简要的介绍和总结,希望本文能够为开发者提供一些参考和帮助,让大家能够更加轻松和高效地使用 Java 集合,总结不易,感兴趣的可以点赞加收藏;后续,也会对常见集合的源码做深入分析,点赞关注不迷路哦。