面试问题浓缩总结 面试问题浓缩总结
  • Go
  • Java
  • C/C++
  • JavaScript/HTML
  • MySQL
  • Redis
  • MongoDB
  • 操作系统
  • 计算机网络
  • spring全家桶
  • mybatis
  • 中间件
  • 软件相关
  • 系统相关
  • 算法
  • 数据结构
  • 设计模式
  • CMU硕士经典100题
  • 剑指offer
  • 重点手撕代码
  • 程序员面试金典
  • 3月
  • 4月
  • 智力题
  • 业务问题
  • 一些技术
  • 安全相关
APP下载 (opens new window)
GitHub (opens new window)
  • Go
  • Java
  • C/C++
  • JavaScript/HTML
  • MySQL
  • Redis
  • MongoDB
  • 操作系统
  • 计算机网络
  • spring全家桶
  • mybatis
  • 中间件
  • 软件相关
  • 系统相关
  • 算法
  • 数据结构
  • 设计模式
  • CMU硕士经典100题
  • 剑指offer
  • 重点手撕代码
  • 程序员面试金典
  • 3月
  • 4月
  • 智力题
  • 业务问题
  • 一些技术
  • 安全相关
APP下载 (opens new window)
GitHub (opens new window)
  • Go

  • JAVA

    • java基础
    • 面向对象
    • 集合框架
      • 线程安全的集合
      • HashMap(重点)
        • HashMap的底层原理
        • Hashmap扩容(1.8以后)
        • 头插法和尾插法
        • 数据插入流程
        • 其他的一些问题
      • LinkedHashMap
      • List
      • Set
      • ConcurrentHashMap
    • 并发框架(JUC)
    • IO NIO框架
    • JVM模型
    • 类加载机制
    • 垃圾回收
    • 参数调优
    • java8特性
    • 面试题
    • 其他
    • Java书籍学习笔记
  • C、C++语言

  • JavaScript和HTML

  • Android相关

  • 程序语言
  • JAVA
小游
2021-03-20

集合框架

集合框架也可以叫容器。(集合是用于存储数据的容器)

image-20210323085103081

常问的有下面几个

  1. ArrayList
  2. LinkList
  3. HashSet
  4. TreeSet
  5. HashMap
  • List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
  • Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
  • Map(用 Key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x”代表 key,"y"代表 value,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。

# 线程安全的集合

  1. ConcurrentHashMap(线程安全的map,这个会考一下那个锁升级的过程,指的是 synchronized 关键字)
  2. CopyOnWriteArrayList(线程安全的list)
  3. CopyOnWriteArraySet(线程安全的set)
  4. Vector和HashTable(早期的线程安全集合,不过被弃用了)

参考:Java线程安全的集合详解_长风-CSDN博客_线程安全的集合有哪些 (opens new window)

# HashMap(重点)

# HashMap的底层原理

在JDK1.8之前,我们使用的是位桶+链表实现

img

1.8之后使用的是位桶+链表+红黑树实现

0-00000031

# Hashmap扩容(1.8以后)

  • 当我们的链表长度超过8时,就会把链表转换为红黑树,长度小于6时有红黑树变成链表
  • 当链表数组(也叫hash表)的容量超过0.75时(加载因子),那么整个HashMap就会扩大为原来的两倍
  • 默认hash表大小为16(为了实现均匀分布)
  • 扩容分两部,第一是创建一个新Entry的数组,长度为原来的两倍,然后在遍历原Entry数组,把所有的Entry重新Hash到新数组。(为什么要Rehash?是因为长度扩大以后,Hash的规则也随之改变。)

# 头插法和尾插法

1.8之前使用的是头插法(即每次插入都是在链表的头部插入的),1.8之后使用的是尾插法,为什么?

因为头插法会存在链表成环的问题

# 数据插入流程

往map插入元素的时候首先通过对key hash然后与数组长度-1进行与运算((n-1)&hash),都是2的次幂所以等同于取模,但是位运算的效率更高。找到数组中的位置之后,如果数组中没有元素直接存入,反之则判断key是否相同,key相同就覆盖,否则就会插入到链表的尾部,如果链表的长度超过8,则会转换成红黑树,最后判断数组长度是否超过默认的长度*负载因子也就是12,超过则进行扩容。

img

# 其他的一些问题

# hashMap使用红黑树而不使用AVL树

最主要的一点是:在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待, 如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!

红黑树和AVL树都是最常用的平衡二叉搜索树,它们的查找、删除、修改都是O(lgn) time

AVL树和红黑树有几点比较和区别: (1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。 (2)红黑树更适合于插入修改密集型任务。 (3)通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试。

总结: (1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。 (2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。 (3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。 (4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。

参考

  1. 《吊打面试官》系列-HashMap (qq.com) (opens new window)
  2. Java中HashMap底层实现原理(JDK1.8)源码分析_tuke_tuke的博客-CSDN博客_hashmap底层 (opens new window)

# 为什么红黑树要小于6才转换,而不是8呢

和hashcode碰撞次数的泊松分布有关,主要是为了寻找一种时间和空间的平衡。 红黑树中的TreeNode是链表中的Node所占空间的2倍,虽然红黑树的查找效率为o(logN),要优于链表的o(N),但是当链表长度比较小的时候,即使全部遍历,时间复杂度也不会太高。固,要寻找一种时间和空间的平衡,即在链表长度达到一个阈值之后再转换为红黑树。

之所以是8,是因为Java的源码贡献者在进行大量实验发现,hash碰撞发生8次的概率已经降低到了0.00000006,几乎为不可能事件,如果真的碰撞发生了8次,那么这个时候说明由于元素本身和hash函数的原因,此次操作的hash碰撞的可能性非常大了,后序可能还会继续发生hash碰撞。所以,这个时候,就应该将链表转换为红黑树了,也就是为什么链表转红黑树的阈值是8。

最后,红黑树转链表的阈值为6,主要是因为,如果也将该阈值设置于8,那么当hash碰撞在8时,会反生链表和红黑树的不停相互激荡转换,白白浪费资源。

<细节向>jdk1.8中HashMap底层链表转红黑树的阈值为什么是8?红黑树转链表为什么是6?_Sirius_7的博客-CSDN博客_hashmap红黑树退化为什么是6 (opens new window)

# Hash冲突解决方法

HashMap采用的是链地址法

  • 开放地址法

    开放定址法也称为再散列法,基本思想就是,如果p=H(key)出现冲突时,则以p为基础,再次hash,p1=H(p),如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址pi。 因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次hash,所以只能在删除的节点上做标记,而不能真正删除节点。

    缺点:容易产生堆积问题;不适合大规模的数据存储;插入时会发生多次冲突的情况;删除时要考虑与要删除元素互相冲突的另一个元素,比较复杂。

  • 再哈希法(双重散列,多重散列)

    提供多个不同的hash函数,当R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。 这样做虽然不易产生堆集,但增加了计算的时间。

  • 链地址法(拉链法)

    链地址法:将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。HashMap采用的就是链地址法来解决hash冲突。(链表长度大于等于8时转为红黑树)

  • 建立公共溢出区

    将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。

# LinkedHashMap

LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 对访问顺序也提供了相关支持。在一些场景下,该特性很有用,比如缓存。具体结构如下:

LinkedHashMap源码详细分析(JDK1.8)_

参考:

LinkedHashMap 源码详细分析(JDK1.8)_慕课手记 (imooc.com) (opens new window)

# List

这个地方问的会少一些

  • ArrayList是基于数组实现的,非线程安全,每次扩容为1.5倍,默认大小为10(源码里:DEFAULT_CAPACITY = 10;),扩容时会创建一个型的大数组,然后把之前的内容拷贝到新数组里
  • LinkedList是基于双向链表,非线程安全,不需要扩容(因为是基于链表),(JDK1.6 之前为循环链表,JDK1.7 取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  • ArrayList最大容量不超过Integer的最大值

适用场景

ArrayList适用于频繁查询和获取数据,LinkedList适合频繁地增加或删除数据

# Set

主要考TreeSet和HashSet,Set 注重独一无二的性质,用于存储无序元素, 值不能重复。

  • HashSet基于HashMap实现,其实就是把HashMaP的值改成空
  • TreeSet基于TreeMap实现
  • LinkedHashSet(继承自HashSet,底层是LinkedHashMap HashSet+LinkedHashMap)
  • BitSet(位集,底层是long数组,用于替代List)
  • CopyOnWriteArraySet(线程安全的,底层是CopyOnWriteArrayList)

参考:java Set学习_冷雨清的博客-CSDN博客 (opens new window)

# ConcurrentHashMap

1.8之前

采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。虽然效率提高了,但是查询遍历的效率还是太低了

img

1.8之后

采用CAS+Synchronized

参考

  1. HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你! | crossoverJie's Blog (opens new window)
  2. JavaGuide (gitee.io) (opens new window)
编辑 (opens new window)
上次更新: 2021/04/23, 21:43:16
面向对象
并发框架(JUC)

← 面向对象 并发框架(JUC)→

Theme by Vdoing | Copyright © 2021-2021 小游
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式