Java 集合知识详解

54 阅读7分钟

概述

Java语言开发经常会使用到集合框架,如下图所示为Java 集合框架结构图:

java集合.png 从图中可以看出Java 集合主要实现了Map和Collection接口。

List、Set、Map 三者区别

List (突出顺序): 存储的元素是有序的、可重复的; ② Set (注重独⼀⽆⼆): 存储的元素是⽆序的、不可重复的; ③ Map: 使⽤键值对(kye-value)存储,类似于数学上的函数 y=f(x),“x”代表key,"y"代表 value,Key 是⽆序的、不可重复的,value 是⽆序的、可重复的,每个键最多映射到⼀个值。

集合底层数据结构

Collection 接⼝下⾯的集合

List 接口 Arraylist : Object[] 数组, Vector : Object[] 数组, LinkedList : 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)。 ② Set 接口 HashSet (⽆序,唯⼀): 基于 HashMap 实现的,底层采⽤ HashMap 来保存元素; LinkedHashSet : LinkedHashSet 是 HashSet 的⼦类,并且其内部是通过 LinkedHashMap 来 实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现⼀样,不过还是 有⼀点点区别的; TreeSet (有序,唯⼀): 红⿊树(⾃平衡的排序⼆叉树)。 ③ Map 接口 HashMap : JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要 为了解决哈希冲突⽽存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较⼤的变 化,当链表⻓度⼤于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时 间; LinkedHashMap : LinkedHashMap 继承⾃ HashMap ,所以它的底层仍然是基于拉链式散列结构 即由数组和链表或红⿊树组成。另外, LinkedHashMap 在上⾯结构的基础上,增加了⼀条双向链 表,使得上⾯的结构可以保持键值对的插⼊顺序。同时通过对链表进⾏相应的操作,实现了访问顺 序相关逻辑; Hashtable : 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存 在的; TreeMap : 红⿊树(⾃平衡的排序⼆叉树)。

如何选择使用集合

① 根据键值获取元素值时就选⽤ Map 接⼝下的集合,需要排序时选择 TreeMap ,不需要排序时选择 HashMap ,保证线程安全就选⽤ConcurrentHashMap ; ② 只需要存放元素值时,就选择实现 Collection 接⼝的集合,保证元素唯⼀时选择实现Set 接⼝的集合⽐如 TreeSet 或 HashSet ,不需要就选择实现 List 接⼝的⽐如 ArrayList 或LinkedList ,然后再根据实现这些接⼝的集合的特点来选⽤。

为什么要使用集合

① 在实际开发中,存储的数据的类型是多种多样的,数组支持的类型较固定,就出现了“集合”; ② 数组的缺点是⼀旦声明之后,⻓度就不可变了,数组存储的数据是有序、可重复的,特点单⼀; ③ 集合提⾼数据存储灵活性,Java 集合不仅⽤来存储不同类型不同数量对象,还可以保存具有映射关系的数据。

集合线程安全选择

常⽤的 Arraylist , LinkedList , Hashmap , HashSet , TreeSet , TreeMap , PriorityQueue 都 不是线程安全的。如果使⽤线程安全集合的话, java.util.concurrent 包中提供很多并发容器: ① ConcurrentHashMap : 可以看作是线程安全的 HashMap ② CopyOnWriteArrayList :可以看作是线程安全的 ArrayList ,在读多写少的场合性能⾮常好,远 远好于 Vector . ③ ConcurrentLinkedQueue :⾼效的并发队列,使⽤链表实现。可以看做⼀个线程安全的 LinkedList ,这是⼀个⾮阻塞队列。 ④ BlockingQueue : 这是⼀个接⼝,JDK 内部通过链表、数组等⽅式实现了这个接⼝。表示阻塞队 列,⾮常适合⽤于作为数据共享的通道。 ⑤ ConcurrentSkipListMap :跳表的实现。这是⼀个 Map ,使⽤跳表的数据结构进⾏快速查找。

集合子接口及方法对比

Arraylist 和 Vector 的区别?

① ArrayList 是 List 主要实现类,底层使⽤ Object[ ]存储,适⽤于频繁的查找⼯作,线程不安全 ; ② Vector 是 List 的古⽼实现类,底层使⽤ Object[ ]存储,线程安全的。

Arraylist 与 LinkedList 区别?

① 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; ② 底层数据结构: Arraylist 底层使⽤的是 Object Object 数组; LinkedList 底层使⽤的是 双向链表,数据结构(JDK1.6 之前为循环链表,JDK1.7 取消了循环。 ③ 插⼊和删除是否受元素位置的影响: ArrayList ArrayList 采⽤数组存储,所以插⼊和删除元素的时间复杂度受元素位置的影响。 ⽐如:执⾏ add(E e) ⽅法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插⼊和删除元素的话( add(int index, E element) )时间复杂度就为 O(n-i)。因为在进⾏上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执⾏向后位/向前移⼀位的操作。 LinkedList LinkedList 采⽤链表存储,所以对于 add(E e) add(E e) ⽅法的插⼊,删除元素时间复杂度不受元素位置的影响,近似O(1),如果是要在指定位置 ii 插⼊和删除元素的话( (add(int index, E element) (add(int index, E element) ) 时间复杂度近似为 o(n)) o(n)) 因为需要先移动到指定位置再插⼊。 ④ 是否⽀持快速随机访问: LinkedList 不⽀持⾼效的随机元素访问,⽽ ArrayList ⽀持。快速随 机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) ⽅法)。 ⑤ 内存空间占⽤: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空间,⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ ArrayList 更多的空间(因为要存放直 接后继和直接前驱以及数据)。

comparable 和 Comparator 的区别

① comparable 接⼝实际上是出⾃ java.lang 包 它有⼀个 compareTo(Object obj) ⽅法⽤来排序; ② comparator 接⼝实际上是出⾃ java.util 包它有⼀个 compare(Object obj1, Object obj2) ⽅法⽤ 来排序。

⽐较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

HashSet 是 Set 接⼝的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值; LinkedHashSet 是 HashSet 的⼦类,能够按照添加的顺序遍历; TreeSet 底层使⽤红⿊树,能够按照添加元素的顺序进⾏遍历,排序的⽅式有⾃然排序和定制排序。

HashMap 和 Hashtable 的区别

① 线程是否安全: HashMap 是⾮线程安全的,HashTable 是线程安全的,因为 HashTable 内部的⽅法基本都经过 synchronized 修饰。(如果保证线程安全就使⽤ ConcurrentHashMap); ② 效率: 因为线程安全,HashMap 要⽐ HashTable 效率⾼⼀点。另外,HashTable 基本被淘 汰,不要在代码中使⽤它; ③ 对 Null key 和 Null value 的⽀持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能⼀个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,NullPointerException。 ④ 初始容量⼤⼩和每次扩充容量⼤⼩的不同 : ① 创建时如果不指定容量初始值,Hashtable 默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化⼤⼩为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为 2 的幂次⽅⼤⼩(HashMap 中的 tableSizeFor() ⽅法保 证,下⾯给出了源代码)。也就是说 HashMap 总是使⽤ 2 的幂作为哈希表的⼤⼩,后⾯会介绍到为 什么是 2 的幂次⽅。 ⑤ 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较⼤变化,当链表⻓度⼤于阈 值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏ 数组扩容,⽽不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。Hashtable 没有这 样的机制。 HashMap 中带有初始容量的构造函数:

public HashMap(int initialCapacity, float loadFactor) {
 if (initialCapacity < 0)
 throw new IllegalArgumentException("Illegal initial capacity: "
+
 initialCapacity);
 if (initialCapacity > MAXIMUM_CAPACITY)
 initialCapacity = MAXIMUM_CAPACITY;
 if (loadFactor <= 0 || Float.isNaN(loadFactor))
 throw new IllegalArgumentException("Illegal load factor: " +
 loadFactor);
 this.loadFactor = loadFactor;
 this.threshold = tableSizeFor(initialCapacity);
 }
 public HashMap(int initialCapacity) {
 this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }
HashMap 和 HashSet 区别

如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码⾮常少,因为除了 clone() 、 writeObject() 、 readObject() 是 HashSet ⾃⼰不得不实现 之外,其他⽅法都是直接调⽤ HashMap 中的⽅法。

HashMapHashSet
实现了 Map 接⼝实现 Set 接⼝
存储键值对仅存储对象
调⽤ put() 向 map中添加元素调⽤ add() ⽅法向 Set 中添加元素
HashMap 使⽤键(Key)计算HashcodeHashSet 使⽤成员对象来计算 hashcode 值,对于两个对象来说hashcode 可能相同,所以 equals()⽅法⽤来判断对象的相等性

制作不易,如果大家看到这里,请给个关注和三连,谢谢大家,后续会继续分享更新其他技术干货。欢迎留言区交流。 更多详细技术视频,欢迎进入B站,点击如下视频地址学习, www.bilibili.com/video/BV1VL… 如果对你有帮助,记得点个三连和关注,感谢。