目录
七、 HashMap、ConcurrentHashMap 区别
一、HashMap的原理,内部数据结构
底层使用哈希表(数组 + 链表),当链表过长会将链表转成 红黑树以实现 O(logn) 时间复杂度内查找
二、 HashMap 中 put 方法过程
1、put(key, value)中直接调用了内部的putVal方法,并且先对key进行了hash操作;
2、putVal方法中,先检查HashMap数据结构中的索引数组表是否位空,如果是的话则进行一次resize操作;
3、以HashMap索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,如果索引指定的位置值为空,则新建一个k-v的新节点;
4、如果不满足的3的条件,则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现;
5、在上面判断流程走完之后,计算HashMap全局的modCount值,以便对外部并发的迭代操作提供修改的Fail-fast判断提供依据,于此同时增加map中的记录数,并判断记录数是否触及容量扩充的阈值,触及则进行一轮resize操作;
6、在步骤4中出现碰撞情况时,从步骤7开始展开新一轮逻辑判断和处理;
7、判断key索引到的节点(暂且称作被碰撞节点)的hash、key是否和当前待插入节点(新节点)的一致,如果是一致的话,则先保存记录下该节点;如果新旧节点的内容不一致时,则再看被碰撞节点是否是树(TreeNode)类型,如果是树类型的话,则按照树的操作去追加新节点内容;如果被碰撞节点不是树类型,则说明当前发生的碰撞在链表中(此时链表尚未转为红黑树),此时进入一轮循环处理逻辑中;
8、循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点作为后继节点,作为后继节点之后并判断当前链表长度是否超过最大允许链表长度8,如果大于的话,需要进行一轮是否转树的操作;如果在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;如果两个条件判断都满足则继续循环,直至进入某一个条件判断然后跳出循环;
9、步骤8中转树的操作treeifyBin,如果map的索引表为空或者当前索引表长度还小于64(最大转红黑树的索引数组表长度),那么进行resize操作就行了;否则,如果被碰撞节点不为空,那么就顺着被碰撞节点这条树往后新增该新节点;
10、最后,回到那个被记住的被碰撞节点,如果它不为空,默认情况下,新节点的值将会替换被碰撞节点的值,同时返回被碰撞节点的值(V)
11、如果桶满了(容量 * 加载因子),就需要 resize。
三、HashMap
1、数组 + 链表方式存储 2、默认容量: 16(2^n 为宜,若定义的初始容量不是 2^n,容量会定义为大于该初始容量的最小 2^n) 例如:初始容量为 13,则真正的容量是 16. 3、put: 1. 索引计算 : ((key.hashCode() ^ (key.hashCode() >>> 16)) & (table.length - 1)) 2.在链表中查找,并记录链表长度,若链表长度达到了 TREEIFY_THRESHOLD(8),则将该链转成红黑树。 3.若在链表中找到了,则替换旧值,若未找到则继续 4.当总元素个数超过容量*加载因子时,扩容为原来 2 倍并重新散列 1.(元素的下标要么不变,要么变为【原下标+原容量】)。 5.将新元素加到链表尾部 6.线程不安全
四、HashTable
1、数组 + 链表方式存储 2、默认容量: 11(质数 为宜) 3、put: 1.索引计算 : (key.hashCode() & 0x7FFFFFFF)% table.length 2.若在链表中找到了,则替换旧值,若未找到则继续 3.当总元素个数超过容量*加载因子时,扩容为原来 2 倍并重新散列。 4.将新元素加到链表头部 4、对修改 Hashtable 内部共享数据的方法添加了 synchronized,保证线程安全。
五、HashMap ,HashTable 区别
- 默认容量不同。
- 索引计算方式不同。
- HashMap 特有的将过长链表转换为红黑树。
- 新元素的位置不同。
- 线程安全性
六、ConcurrentHashMap 原理
1、最大特点是引入了 CAS(借助 Unsafe 来实现【native code】) 1.CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存 值V修改为B,否则什么都不做。 2.Unsafe 借助 CPU 指令 cmpxchg 来实现 3.使用实例:
对 sizeCtl 的控制都是用 CAS 来实现的
sizeCtl :默认为0,用来控制 table 的初始化和扩容操作。
-1 代表table正在初始化
N 表示有 -N-1 个线程正在进行扩容操作
如果table未初始化,表示table需要初始化的大小。
如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。
4.CAS 会出现的问题:ABA 对变量增加一个版本号,每次修改,版本号加 1,比较的时候比较版本号。
七、 HashMap、ConcurrentHashMap 区别
1、索引计算消除了最高位的影响 2、默认容量: 16(若定义了初始容量(c),容量会定义为大于(c + (c >>> 1) +1) 的最小 2^n) 例如:初始容量为 13,则真正的容量是 32. 3、线程安全,并发性能较好 并发性能好的原因是 ConcurrentHashMap 并不是定义 synchronized 方法,而是在链表头上同步,不同的链表之间是互不影响的。
八、HashMap 中 hash 函数怎么是是实现的?
- 高 16bit 不变,低 16bit 和高 16bit 做了一个异或
- (n - 1) & hash --> 得到下标
- 抛开 HashMap,hash 冲突有那些解决办法:开放定址,链地址法
九、数组和 ArrayList 的区别
- 数组可以包含基本类型和对象类型,ArrayList 只能包含对象类型
- 数组大小固定,ArrayList 大小可以动态变化
- ArrayList 提供了更多的特性(
addAll
、removeAll
)。
十、红黑树的特点及相比平衡二叉树的优点(先介绍各自特点)
1.红黑树
每个节点要么是红色,要么是黑色。
根节点永远是黑色的。
所有的叶节点都是空节点(即 null),并且是黑色的。
每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)
从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。
2.平衡二叉树
任何节点的两个儿子子树的高度最大差别唯一
3.红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。
十一、Trie-Tree 原理及其应用
1、字典树 2、特点
根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
每个节点的所有子节点包含的字符互不相同。
3、核心思想是空间换时间 4、应用
字符串检索
词频统计
十二、Arrays 静态类如何实现排序的?
- 双轴快排
- 首先检查数组长度,如果比阀值(286)小,直接使用双轴快排
- 否则先检查数组中数据的连续性,标记连续升序,反转连续降序,如果连续性好,使用 TimSort 算法(可以很好的利用数列中的原始顺序)
- 否则使用双轴快排 + 成对插入排序