zoukankan      html  css  js  c++  java
  • HashMap复习

    面试考察的绝对重点。以下是问题导航

    1. hashMap数据结构
    2. hash函数设计
    3. 扩容 为什么扩容为2
    4. Put/get方法
    5. 链表转红黑树
    6. 1.7 & 1.8差别
    7. 死循环条件

    基础知识

    ^ 异或:相同的为0 不同的为1
    & 与运算:A与B都为1结果为1 其他为0
    | 或运算:A或B其中一个为1则为1 其他都为0

    hashMap数据结构

    略。

    hash函数设计

    int h = (key == null) ? 0 :(h = key.hashcode)^(h>>>16)
    int index = h & (length-1);
    
    1. h>>>16 被称为扰动函数 使用无符号右移16位后再与原hashcode异或 原因是 .hashcode 方法计算的结果为32位,但是length位数比较低,直接计算index高位不参与容易冲突,右移16再异或可以混合低位和高位增加随机性。从打降低hash冲突的风险。
    2. 为什么是 &(length-1): Java中对2的n次方取余等于减一的与运算,效率更高。

    扩容步骤

    resize()方法中若数组未初始化则初始化数组,若是已经初始化的数组,则数组扩容为之前的2倍。扩容过程如下

    1. 遍历数组若旧数组中不存在hash冲突的节点,直接移动到新的数组当中去。int index = (e.hashcode&(newLength -1))
    2. 若存在冲突的节点,树节点进行拆分,链表节点会保持原有顺序,依然是尾插法。
    3. 判断元素是否在原有位置e.hashcode&(oldcap)==0 这是因为oldCap的高位和newCap-1的高位是一致的。
    4. 发现元素不是在原有位置,更新hitail和hiHead的指引关系。
    5. 将index未改变的复制到新的数组当中去。
    6. 将index发生改变的元素复制到新数组当中去。新的下标为oldindex+oldCap

    为什么扩容为2

    因为在扩容中判断元素是否在原位置使用的是与操作。都为1才能为1。假设数组从8扩容到16,决定为扩容后是否在原位置的为hashCode的高位值。1/0会被分流到不同的位置当中去,从而在扩容中可以让数组分布更加均匀。

    old.length -1 = 7   0 0 1 1 1
    e.hashCode = k      x p x x x
    ==============================
                        0 0 y y y 
    扩容前index值由低三位决定,与操作让高位以上都为0                                        
    e.hashcode&(oldCap-1)
    
    new.length -1 = 15   0 1 1 1 1
    e.hashCode = k       x p x x x
    ==============================
                         0 z y y y
    扩容后唯一发生变化的是高位z。若z为0那么位置不变。
    若z的位置为1 那么新的index等于oldCap+oldIndex
    新的index的值为zyyy z000等于oldCap 0yyy等于oldIndex
    e.hashcode&(newCap-1)
    
    old.length = 8       0 1 0 0 0
    e.hashCode = k       x p x x x
    ==============================
                         0 z 0 0 0
    此时e.hashcode & oldCap == 0 那么则z为0 说明位置不变。                     
    

    put/get方法

    get方法

    1. hash & (length -1)确定元素位置,如果没碰撞直接放到bucket里;
    2. 如果碰撞了,以链表的形式存在buckets后;
    3. 如果碰撞导致链表过长(就把链表转换成红黑树(JDK1.8中的改动 大于等于8);
    4. 如果节点已经存在就替换old value(保证key的唯一性)
    5. 如果bucket满了(超过load factor*current capacity),就要resize。

    put方法

    1. hash & (length -1)确定元素位置
    2. 判断第一个元素是否是我们要找的位置
    3. 判断节点是否为树,若是在树节点查找
    4. 判断节点是否为链表,若是在链表查找
    5. 找到对应的元素返回,无则返回空值

    死循环

    在JDK1.7当中由于头插法在并发情况下会出现成环的情况。假设数组index[1]=1->5->9

    1. 当前A线程正在准备扩容 e=1 e.next=5让出时间片 B线程完成扩容
    2. 扩容后的newtabIndex[1]=9->5->1 由于头插法 顺序被置换
    3. 此时A线程继续执行 newtabIndex[1]会挂载到e=1上
    4. 然后执行 newtabIndex[1] = e.next 给出一个 e=1 指向e.next =5的引用导致成环
    5. 在当前位置getKey时候就会引起死循环问题。
  • 相关阅读:
    C++ 中static 使用大全
    JsonCpp 简单使用
    [转]C++ string的trim, split方法
    线程函数
    C++ 读写文件流
    JQ_返回顶部
    JQ_开发经验
    JQ_五星级评分特效
    JQ_One()函数特效
    JQ_插件开发
  • 原文地址:https://www.cnblogs.com/threecha/p/15068725.html
Copyright © 2011-2022 走看看