题外话:如何学习
逻辑思维能力是梳理学习方法的基础。养成线性思维:两个或者多个概念,像一条线串起来。
演绎推导法
示例:因果推理。因为JAVA中网络编程只提供了BIO和NIO两种方式,所以一切框架中,涉及到网络处理的,都可以用这两个知识点去探究原理。
归纳总结法
示例:可能正确的猜想。线上10台服务器,有三台总是每天会自动重启,收集相关信息后,发现是运维在修改监控系统配置的时候,漏掉了提高这三台机器的重启阈值。
类比法
- 集群:就好像是马在拉车,一匹马拉不动的时候,就使用多匹马去拉。
- 单台服务器承受不来的 QPS,由多台服务器去均衡
- 分布式:就像是理发的过程中,洗头发和剪头发是不同的人负责的。
- 一个用户的请求,由多个系统协同完成
深入理解 HashMap
实现推理
数据要存储
- 涉及到数据结构:数组、链表、栈、树、队列
数组的插入和查找
➢ 顺序查找:插入时按先后顺序插入,查找时轮询扫描进行比对。
➢ 二分查找:插入时进行排序;查找时将n个元素分成大致相等的两部分,减少复杂度。
➢ 分块查找:分块查找是二分查找和顺序查找的一种改进。
➢ 哈希表:对元素的关键信息进行hash计算,求出下标后直接插入或查找。常用的实现是除留余数法。
put方法
JDK 1.7 实现
- 使用的是哈希表
- 默认的 capacity 为 16,负载因子 0.75
- 如果key为null,则放到 table[0]
- 先 key 取 hash,然后对当前的数组长度取模
- 这里,看如果未指定 capacity,则默认为16.
- 然后放,如果hash冲突,比较equals。相同则覆盖
- 放之前会检查大小,如果当前大小达到了threshold,则扩容到 capacity * 2
- threshold 默认为 capacity * 0.75
- 在这个过程中,会遍历每一个数组,进行 rehash ,在 transfer 方法中。
- 如果通过了,则 new 一个 entry 放入
- 这里,如果 hash 冲突了,则会生成一个含有 next 指针的链表
JDK 1.8 改进
- 当链表长度达到 TREEIFY_THRESHOLD(8)以后,会转换为红黑树
- resize 扩容操作,不再是 1.7 的达到 threshold && 数组当前位置有 entry,改成了每次 put 完成,统计 modCount 以后
其他
为什么要有 threshold
- 当 size = threshold 后,就需要扩容
- 因为 size 越大,代表 hash 冲突越多,遍历 数组的时间复杂度是 O(1),但遍历链表,则为 O(n)
threshold 都什么时候变化
- 构造函数中,默认为 16
- 第一次 put 的时候,会变为 capacity * loadFactor
- 超过后,为扩容后的大小 * loadFactor
是否是达到 threshold 就会扩容
- 不是,需要达到 threshold && 通过 key hash 取模的位置上,不为null
为什么 hashMap 线程不安全
- 因为在 createEntry 的时候,有size ++
- ++ 本来就不是原子操作
- 如果达到了 threshold,会出问题
为什么临界值是 8 的时候转为红黑树
- 少量数据的时候,链表查找方便。而树结构徒增性能损耗
TODO
- 顺序查找、二分查找、分块查找、哈希表、红黑树