java-集合框架底层数据结构总结

我会带着你远行 2021-11-10 08:50 506阅读 0赞

简单图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4MDMzMjM5_size_16_color_FFFFFF_t_70

1. List

  • Arraylist: Object数组
  • Vector: Object数组
  • LinkedList: 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环)

2. Set

  • HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)

Map

  • HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
  • LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
  • Hashtable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap: 红黑树(平衡二叉排序树)

-———————————————————————————————————

Arraylist说明:

底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

数据结构-线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)

  ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

  ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速访问-随机存取。

  ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆

  ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化能通过序列化去传输

  和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList

LinkedList说明:

LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

  1. List list=Collections.synchronizedList(new LinkedList(...));

HashMap 说明:

DK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

  1. DK 1.8 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
  2. static final int hash(Object key) {
  3. int h;
  4. // key.hashCode():返回散列值也就是hashcode
  5. // ^ :按位异或
  6. // >>>:无符号右移,忽略符号位,空位都以0补齐
  7. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  8. }
  9. 对比一下 JDK1.7 HashMap hash 方法源码.
  10. static int hash(int h) {
  11. // This function ensures that hashCodes that differ only by
  12. // constant multiples at each bit position have a bounded
  13. // number of collisions (approximately 8 at default load factor).
  14. h ^= (h >>> 20) ^ (h >>> 12);
  15. return h ^ (h >>> 7) ^ (h >>> 4);
  16. }
  17. 相比于 JDK1.8 hash 方法 JDK 1.7 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。

#

HashMap多线程操作导致死循环问题——

主要原因在于并发下的Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。

【为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!

new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。】

JDK提供的并发容器大部分在 java.util.concurrent 包中。

  • ConcurrentHashMap: 线程安全的HashMap
  • CopyOnWriteArrayList: 线程安全的List,在读多写少的场合性能非常好,远远好于Vector.
  • ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。
  • BlockingQueue: 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。
  • ConcurrentSkipListMap: 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。

…待更新

发表评论

表情:
评论列表 (有 0 条评论,506人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java集合框架总结

    java集合框架有不少子类,并且个子类对象特点不同,特别是初学者经常对此感到迷惑。那么如何有效区分他们呢? 下面是我总结的一些技巧,希望对各位初学者有用 集合框架

    相关 集合数据结构总结

     List  有序集合 ArrayList 结构:采用数组结构,查询性能优秀(数组将元素在内存中连续存放,链表非顺序存放),插入以及删除性能较差(插入以及删除都会