加载中...
avatar
文章
42
标签
25
分类
21
首页
Java
Spring全家桶
  • Spring
  • SpringBoot
  • SpringCloud
JVM
源码
  • Mybatis
  • HashMap
归档
其他
  • 互联网电子书汇总
  • JAVA八股文指南
  • 历史
  • 相册
关于
Logo码农StormlingJava系列(八)| Java集合
搜索
首页
Java
Spring全家桶
  • Spring
  • SpringBoot
  • SpringCloud
JVM
源码
  • Mybatis
  • HashMap
归档
其他
  • 互联网电子书汇总
  • JAVA八股文指南
  • 历史
  • 相册
关于

Java系列(八)| Java集合

发表于2021-12-25|更新于2025-01-16|Java
|总字数:2.6k|阅读时长:8分钟|浏览量:

Java核心技术 集合类

Java 集合,也叫作容器,主要是由两大接口派生而来:一个是 Collection 接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。

Collection 接口关系图

具体实现:List、Queue、Set,可以看下Collection关系图

image-20250107162007452

Map 键值对关系图

具体实现:Hashtable、LinkedHashMap、TreeMap,可以看下Map 关系图

image-20250107162107726

1.List、Set、Queue、Map 区别

  • List:有序列表,可重复
  • Set:无序列表,不可重复
  • Queue:特性的排队顺序确定先后关系,存储元素是有序的,可重复
  • Map:使用键值对(key-value)存储

2.List、Set、Map 常用集合有哪些?

List

  • vector: 底层是数组,方法加了 synchronized 来保证线程安全,所以效率较慢,使用 ArrayList 替代。
  • ArrayList: 线程不安全,底层是数组,因为数组都是连续的地址,所以查询比较快。增删比较慢,增会生成一个新数组,把新增的元素和原有元素放到新数组中,删除会导致元素移动,所以增删速度较慢。
  • LinkedList: 线程不安全,底层是链表,因为地址不是连续的,都是一个节点和一个节点相连,每次查询都得重头开始查询,所以查询慢,增删只是断裂某个节点对整体影响不大,所以增删速度较快。

Set

  • HashSet: 底层是哈希表 (数组 + 链表或数组 + 红黑树),在链表长度大于 8 时转为红黑树,在红黑树节点小于 6 时转为链表。其实就是实现了 HashMap,值存入 key,value 是一个 final 修饰的对象。
  • TreeSet: 底层是红黑树结构,就是 TreeMap 实现,可以实现有序的集合。String 和 Integer 可以根据值进行排序。如果是对象需要实现 Comparator 接口,重写 compareTo() 方法制定比较规则。
  • LinkedHashSet: 实现了 HashSet,多一条链表来记录位置,所以是有序的。

Map<key,value> 双例结构

  • TreeMap: 底层是红黑树,key 可以按顺序排列。
  • HashMap: 底层是哈希表,可以很快的储存和检索,无序,大量迭代情况不佳。
  • LinkedHashMap: 底层是哈希表 + 链表,有序,大量迭代情况佳。

3.ArrayList 的初始容量是多少?扩容机制是什么?扩容过程是怎样?

  • 初始容量: 默认 10,也可以通过构造方法传入大小。

  • 扩容机制: 原数组长度 + 原数组长度 / 2 (源码中是原数组右移一位,也就相当于除以 2)
    注意:扩容后的 ArrayList 底层数组不是原来的数组。

  • 扩容过程: 因为 ArrayList 底层是数组,所以它的扩容机制和数组一样,首先新建一个新数组,长度是原数组的 1.5 倍,然后调用 Arrays.copyof() 复制原数组的值,然后赋值给新数组。

4. 什么是哈希表

​ 根据关键码值 (Key value) 而直接进行访问的数据结构,在一个表中,通过 **H(key)计算出 key 在表中的位置,H(key)**就是哈希函数,表就是哈希表。

5. 什么是哈希冲突

​ 不同的 key 通过哈希函数计算出相同的储存地址,这就是哈希冲突。

6. 解决哈希冲突

  • 开放地址法:如果发生哈希冲突,就会以当前地址为基准,再去寻找计算另一个位置,直到不发生哈希冲突。 寻找的方法有:
  1. 线性探测 1,2,3,m
  2. 二次探测 1 的平方,-1 的平方,2 的平方,-2 的平方,k 的平方,-k 的平方,k<=m/2
  3. 随机探测 生成一个随机数,然后从随机地址 + 随机数 ++。
  • 链地址法 :冲突的哈希值,连到到同一个链表上。

  • 再哈希法 (再散列方法) :多个哈希函数,发生冲突,就在用另一个算计,直到没有冲突。

  • 建立公共溢出区 :哈希表分成基本表和溢出表,与基本表发生冲突的都填入溢出表。

7.HashMap 的 hash() 算法,为什么不是 h=key.hashcode(), 而是 key.hashcode()^ (h>>>16)

​ 得到哈希值然后右移 16 位,然后进行异或运算,这样使哈希值的低 16 位也具有了一部分高 16 位的特性,增加更多的变化性,减少了哈希冲突。

8. 为什么 HashMap 的初始容量和扩容都是 2 的次幂

​ 因为计算元素存储的下标是 (n-1)& 哈希值,数组初始容量 -1,得到的二进制都是 1,这样可以减少哈希冲突,可以更好的均匀插入。

9.HashMap 如果指定了不是 2 的次幂的容量会发生什么?

​ 会获得一个大于指定的初始值的最接近 2 的次幂的值作为初始容量。

10.HashMap 为什么线程不安全

  • jdk1.7 中因为使用头插法,再扩容的时候,可能会造成闭环和数据丢失。
  • jdk1.8 中使用尾插法,不会出现闭环和数据丢失,但是在多线程下,会发生数据覆盖。
    • put 操作中,在 putVal 函数里,值的覆盖还有长度的覆盖。

11. 解决 Hashmap 的线程安全问题

​ (1) 使用 Hashtable 解决,在方法加同步关键字,所以效率低下,已经被弃用。
​ (2) 使用 Collections.synchronizedMap(new HashMap<>()), 不常用。
​ (3)ConcurrentHashMap(常用)

12.ConcurrentHashMap 的原理

  • jdk1.7: 采用分段锁,是由 Segment(继承 ReentrantLock:可重入锁,默认是 16,并发度是 16) 和 HashEntry 内部类组成,每一个 Segment(锁) 对应 1 个 HashEntry(key,value) 数组,数组之间互不影响,实现了并发访问。
  • jdk1.8: 抛弃分段锁,采用 CAS(乐观锁)+synchronized 实现更加细粒度的锁,【Node 数组 + 链表 + 红黑树】结构。只要锁住链表的头节点 (树的根节点),就不会影响其他数组的读写,提高了并发度。

13. 为什么用 synchronized 代替 ReentrantLock

​ ①节省内存开销。ReentrantLock 基于 AQS 来获得同步支持,但不是每个节点都需要同步支持,只有链表头节点或树的根节点需要同步,所以使用 ReentrantLock 会带来很大的内存开销。
​ ②获得 jvm 支持,可重入锁只是 api 级别,而 synchronized 是 jvm 直接支持的,能够在 jvm 运行时做出相应的优化。
​ ③在 jdk1.6 之后,对 synchronized 做了大量的优化,而且有多种锁状态,会从 【无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁】一步步转换。

​ AQS (Abstract Queued Synchronizer): 一个抽象的队列同步器,通过维护一个共享资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架。

14.HashMap 为什么使用链表

​ 减少和解决哈希冲突,把冲突的值放在同一链表下。

15.HashMap 为什么使用红黑树

​ 当数据过多,链表遍历较慢,所以引入红黑树。

16.HashMap 为什么不一上来就使用红黑树

​ 维护成本较大,红黑树在插入新的数据后,可能会进行【变色、左旋、右旋】来保持平衡,所以当数据少时,就不需要红黑树。

17. 说说你对红黑树的理解(❕❕需要扩展)

​ ①根节点是黑色。

​ ②节点是黑色或红色。

​ ③叶子节点是黑色。

​ ④红色节点的子节点都是黑色。

​ ⑤从任意节点到其子节点的所有路径都包含相同数目的黑色节点。

红黑树从根到叶子节点的最长路径不会超过最短路径的 2 倍。保证了红黑树的高效。

18. 为什么链表长度大于 8,并且表的长度大于 64 的时候,链表会转换成红黑树?

​ 因为链表长度越长,哈希冲突概率就越小,当链表等于 8 时,哈希冲突就非常低了,是千万分之一,我们的 map 也不会存那么多数据,如果真要存那么多数据,那就转为红黑树,提高查询和插入的效率。

19. 为什么转成红黑树是 8 呢?而重新转为链表阈值是 6 呢?

​ 因为如果都是 8 的话,那么会频繁转换,会浪费资源。

20. 为什么负载因子是 0.75?(冲突和利用率)

​ 加载因子越大,填满的元素越多,空间利用率越高,但发生冲突的机会变大了;

​ 加载因子越小,填满的元素越少,冲突发生的机会减小,但空间浪费了更多了,而且还会提高扩容 rehash 操作的次数。

​ “冲突的机会”与 “空间利用率” 之间,寻找一种平衡与折中。

​ 又因为根据泊松分布,当负载因子是 0.75 时,平均值是 0.5,带入可得,当链表为 8 时,哈希冲突发生概率就很低了。

21. 什么时候会扩容?

​ 元素个数 > 数组长度 * 负载因子 例如 16 * 0.75 = 12, 当元素超过 12 个时就会扩容。
​ 链表长度大于 8 并且表长小于 64,也会扩容

22. 为什么不是满了扩容?

​ 因为元素越多,空间利用率是高了,但是发生哈希冲突的几率也增加了。

23. 扩容过程

  • jdk1.7: 会生成一个新 table,重新计算每个节点放进新 table,因为是头插法,在线程不安全的时候,可能会出现闭环和数据丢失。
  • jdk1.8: 会生成一个新 table,新位置只需要看 (e.hash & oldCap) 结果是 0 还是 1;0 就放在旧下标,1 就是旧下标 + 旧数组长度。避免了对每个节点进行 hash 计算,大大提高了效率。e.hash 是数组的 hash 值,,oldCap 是旧数组的长度。

24.HashMap 和 Hashtable 的区别

HashTable HashMap
Key Value 不能 null Key Value 可为 null
线程安全 线程不安全

25. 集合为什么要用迭代器 (Iterator)

​ 更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如果不用迭代器,只能 for 循环,还必须知道集合的数据结构,复用性不强。

参考自:

java 集合框架详解

HashMap 底层原理详解

文章作者: stormling
文章链接: http://www.stormling.top/posts/19057.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 码农Stormling!
Java
cover of previous post
上一篇
Java系列(九)| Java多线程
多线程java 多线程及线程池原理讲解 1. 线程是什么?多线程是什么?​ 线程: 是最小的调度单位,包含在进程中。​ 多线程: 多个线程并发执行的技术。 2. 守护线程和用户线程守护线程: jvm 给的线程。比如:GC 守护线程。 用户线程: 用户自己定义的线程。比如:main() 线程。 拓展: Thread.setDaemon(false) 设置为用户线程 Thread.setDaemon(true) 设置为守护线程 3. 线程的各个状态 新建 (New): 新建一个线程。 就绪 (Runnable): 抢夺 cpu 的使用权。 运行 (Running): 开始执行任务。 阻塞 (Blocked): 让线程等待,等待结束进入就绪队列。 死亡 (Dead): 线程正常结束或异常结束。 4. 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等 wait(): 线程等待,会释放锁,用于同步代码块或同步方法中,进入等待状态 sleep(): 线程睡眠,不会释放锁,进入超时等待状态 ...
cover of next post
下一篇
网络系列(一)| TCP 快速入门
简介互联网有一整套协议构成,TCP是其中的一层 以太网 作用:电子信号如何组成数据包(packet),解决了子网内部点对点通信 缺点:无法连接多个局域网,IP协议解决 IP 作用:定义一套自己的地址规则,通过路由的形式将各个局域网相连。路由器基于IP协议 路由器内部有一张路由表,规定 IP 出口,实现数据包转发 缺点:是地址协议,不能保证数据完整,TCP 协议解决 TCP 作用: 保证数据通信的完整性和可靠性,防止丢包 TCP 数据包以太网的数据包 大小固定,最初1518 增加到 1522 字节 。其中头信息(head)22 字节,负载(payload):1500字节 以太网数据包:1522 字节 头信息:22 负载:1500 IP数据包 头信息:20(最少) 负载:1480 TCP 数据包 头信息:20 (最少) 负载:1400 TCP 数据包编号(SEQ)TCP 为每个数据包编号(sequence number),目的是为了接收方按照顺序还原。另外,万一丢包也可知道丢失哪一个包。 每个数据包都有两个编号 自身编号:sequence...

评论
ValineGitalk
avatar
stormling
文章
42
标签
25
分类
21
Follow Me
公告
欢迎大家来到Stormling博客
目录
  1. 1. Java核心技术 集合类
  2. 2. Collection 接口关系图
  3. 3. Map 键值对关系图
  4. 4. 1.List、Set、Queue、Map 区别
  5. 5. 2.List、Set、Map 常用集合有哪些?
    1. 5.1. List
    2. 5.2. Set
    3. 5.3. Map<key,value> 双例结构
  6. 6. 3.ArrayList 的初始容量是多少?扩容机制是什么?扩容过程是怎样?
  7. 7. 4. 什么是哈希表
  8. 8. 5. 什么是哈希冲突
  9. 9. 6. 解决哈希冲突
  10. 10. 7.HashMap 的 hash() 算法,为什么不是 h=key.hashcode(), 而是 key.hashcode()^ (h>>>16)
  11. 11. 8. 为什么 HashMap 的初始容量和扩容都是 2 的次幂
  12. 12. 9.HashMap 如果指定了不是 2 的次幂的容量会发生什么?
  13. 13. 10.HashMap 为什么线程不安全
  14. 14. 11. 解决 Hashmap 的线程安全问题
  15. 15. 12.ConcurrentHashMap 的原理
  16. 16. 13. 为什么用 synchronized 代替 ReentrantLock
  17. 17. 14.HashMap 为什么使用链表
  18. 18. 15.HashMap 为什么使用红黑树
  19. 19. 16.HashMap 为什么不一上来就使用红黑树
  20. 20. 17. 说说你对红黑树的理解(❕❕需要扩展)
  21. 21. 18. 为什么链表长度大于 8,并且表的长度大于 64 的时候,链表会转换成红黑树?
  22. 22. 19. 为什么转成红黑树是 8 呢?而重新转为链表阈值是 6 呢?
  23. 23. 20. 为什么负载因子是 0.75?(冲突和利用率)
  24. 24. 21. 什么时候会扩容?
  25. 25. 22. 为什么不是满了扩容?
  26. 26. 23. 扩容过程
  27. 27. 24.HashMap 和 Hashtable 的区别
  28. 28. 25. 集合为什么要用迭代器 (Iterator)
  29. 29. 参考自:
最新文章
面向八股文面试专场
面向八股文面试专场2025-01-22
【每日早报】-2025-01-21 - 星期二
【每日早报】-2025-01-21 - 星期二2025-01-21
规则引擎 Drools 8+ 快速入门
规则引擎 Drools 8+ 快速入门2024-12-11
数据库系列(二) | Mybatis Plus 3.0+快速入门
数据库系列(二) | Mybatis Plus 3.0+快速入门2024-12-09
分布式系列(二) | Redisson分布式锁
分布式系列(二) | Redisson分布式锁2024-12-05
©2019 - 2025 By stormling
码农Stormling程序员,关注公众号【码农Stormling】回复【面试】获取最全面试pdf
搜索
数据加载中