哈希函数
- 将任意长的输入映射成固定长的输出的比特串
h:{0.1}^* -> {0,1}^n
- 计算机科学领域:
- 表示将关键字的集合映射到地址集合上,实现记录的快速查找
- 在密码学中:
- 将任意长度的消息压缩成固定长度的哈希值(称为消息摘要或数字指纹签名等)
jdk1.7中hashmap组成
- 数组+链表
- 加入新值:
- 根据key值获取一个哈希值
- 由哈希值与当前数组大小取模确定放入位置
- 在对应位置放入数组元素对应的链表中 更改原元素对应链表的头链指向(新加入的值存在在该链表的头链) 完成加入新值
- 获取值:
- 根据key值获取对应的哈希值x
- 由获取到的哈希值x以及数组大小 找到数组中的元素
- 遍历该元素下的链表 当里面某个元素的哈希值等于x时返回该元素 否则返回null
jdk1.8中hashmap组成
- 数组+红黑树
- 弥补1.7中使用链表的不足:即便哈希映射 难免会造成某个链表过于长 导致查询效率降低
- 红黑树 当某个链表长度大于8时 该链表将自动被触发改为红黑树(查找效率log2n,也叫自平衡二叉查找树)
- 加入新值:
- 根据key值获取一个哈希值
- 由哈希值与当前数组大小取模确定放入位置
- 判断对应位置元素是否是树结点
- 如果不是树节点 则是链表 则
- 在对应位置放入数组元素对应的链表中 移动指向到链尾 并计数器加一 然后更改原链尾元素的next指向(新加入的值存在在该链表的链尾) 完成加入新值 若加入后计数器大于8 则进行链表红黑树化
- 如果是树结点 按照红黑树的规则加入新元素
- 不管是树或者链表 因为新加入元素在链尾 因此扩容时不用担心多线程下形成死循环问题 不会造成死锁(扩容时依旧不改变元素顺序)
数组容量有关
- 当使用hashmap时传入初始数组容量的值 实际的容量值会是大于传入值的第一个2次方数(调了源码内部函数获取)
- 所有的2次方数-1得到的数x 对应的源码都是前4n位为0后4n位全为1 这样进行异或运算 就可以得到被运算数的后几位源码值z
- 上述性质 就是很多时候要使用2次方数 然后减一进行异或的原因
- 若要让前4n位也参与到运算哈希 则把x右移4n位得到y 再用y与z进行一次异或 这样就可以充分运用到原数据了
hashmap扩容
- 当当前数组大小>阈(yu)值(=当前数组容量乘以装载因子0.75)时开始扩容
- 使用双重循环 运用与jvm中的复制标记方法类似思想 先申请足够大的空间 再逐个放入 先数组元素 再每个元素下对应的链表
- 在jdk1.7下 当使用多线程进行扩容时 可能会导致移动链表全部元素时 产生循环链表造成死循环(扩容前后链表元素顺序会被掉头 next指向可能会混乱) 然后链表元素总移不完 形成“死锁”
concurrenthashmap
- 像hashmap的升级版 保证并发安全
- jdk1.7:
- 在hashmap基础上 加了分段锁 即在数组之上 相邻两个数组元素归为一个segment 整个ConcurrentHashMap由segment数组组成
- segment继承了ReentranLock 故自带了一把可冲入锁 单个segment是一个小型的hashmap
- 这样保证那下面的两个链表或者树的操作线程安全 同时 锁的粒度不至于过大(如hashtable 在每个方法上加锁 粒度太大) 影响效率
- jdk1.8:
- 在hashmap基础上 考虑到每个线程中的put操作 都要访问数组元素 再访问旗下的链表或者树(对头节点加锁了)
- 在put方法中 对数组元素加锁 这样就可以更细化粒度 也保证并发安全
2906

被折叠的 条评论
为什么被折叠?



