zoukankan      html  css  js  c++  java
  • HashMap中的hash算法总结

    前言

    算法一直是我的弱项,然而面试中基本是必考的项目,刚好上次看到一个HashMap的面试题,今天也来学习下 HashMap中的hash算法是如何实现的。

    数学知识回顾

    • << : 左移运算符,num << 1,相当于num乘以2 低位补0
      举例:3 << 2
      将数字3左移2位,将3转换为二进制数字0000 0000 0000 0000 0000 0000 0000 0011,然后把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,最后在低位(右侧)的两个空位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 1100,则转换为十进制是12。
      数学意义:
      在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。

    • >>: 右移运算符
      举例:11 >> 2
      则是将数字11右移2位,11 的二进制形式为:0000 0000 0000 0000 0000 0000 0000 1011,然后把低位的最后两个数字移出,因为该数字是正数,所以在高位补零。则得到的最终结果是0000 0000 0000 0000 0000 0000 0000 0010。转换为十进制是3。
      数学意义:
      右移一位相当于除2,右移n位相当于除以2的n次方。这里是取商哈,余数就不要了。

    • >>> : 无符号右移,忽略符号位,空位都以0补齐
      按二进制形式把所有的数字向右移动对应位数,低位移出(舍弃),高位的空位补零。对于正数来说和带符号右移相同,对于负数来说不同。 其他结构和>>相似。

    • % : 模运算 取余
      简单的求余运算

    • ^ : 位异或 第一个操作数的的第n位于第二个操作数的第n位相反,那么结果的第n为也为1,否则为0
      0^0=0, 1^0=1, 0^1=1, 1^1=0

    • & : 与运算 第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
      0&0=0, 0&1=0, 1&0=0, 1&1=1

    • | : 或运算 第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0
      0|0=0, 0|1=1, 1|0=1, 1|1=1

    • ~ : 非运算 操作数的第n位为1,那么结果的第n位为0,反之,也就是取反运算(一元操作符:只操作一个数)
      ~1=0, ~0=1

    HashMap中的hash算法

    首先要明白一个概念,HashMap中定位到桶的位置 是根据Key的hash值与数组的长度取模来计算的。
    具体的细节我就不说了,默认认为大家都懂这一点。
    取模可以改为:hashCode & (length - 1)
    看下JDK8中的hash 算法:

     static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }

    首先是取key的hashCode算法,然后对16进行异或运算和右移运算。
    在分析上面异或运算和右移运算问题之前,我们需要先看看另一个事情,什么呢?就是 HashMap 如何根据 hash 值找到数组种的对象,我们看看 get 方法的代码:

      final Node<K,V> getNode(int hash, Object key) {
            Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
            if ((tab = table) != null && (n = tab.length) > 0 &&
                // 我们需要关注下面这一行
                (first = tab[(n - 1) & hash]) != null) {
                if (first.hash == hash && // always check first node
                    ((k = first.key) == key || (key != null && key.equals(k))))
                    return first;
                if ((e = first.next) != null) {
                    if (first instanceof TreeNode)
                        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            return e;
                    } while ((e = e.next) != null);
                }
            }
            return null;
        }

    我们看看代码中注释下方的一行代码:first = tab[(n - 1) & hash])。
    使用数组长度减一 与运算 hash 值。这行代码就是为什么要让前面的 hash 方法移位并异或。


    ** 我们分析一下:**
    首先,假设有一种情况,对象 A 的 hashCode 为 1000010001110001000001111000000,对象 B 的 hashCode 为 0111011100111000101000010100000。


    如果数组长度是16,也就是 15 与运算这两个数(前面说的hashCode & (length - 1)), 你会发现结果都是0。这样的散列结果太让人失望了。很明显不是一个好的散列算法。


    但是如果我们将 hashCode 值右移 16 位,也就是取 int 类型的一半,刚好将该二进制数对半切开。并且使用位异或运算(如果两个数对应的位置相反,则结果为1,反之为0),这样的话,就能避免我们上面的情况的发生。简而言之就是尽量打乱hashCode的低16位,因为真正参与运算的还是低16位。

    不知道这种解释是否是简单明了,经过自己的思考和分析后 也明白了 这段代码设计的初衷,也会感叹设计者的精妙。


    如有疑问欢迎留言,如有遗漏或错误的地方也欢迎指出。



  • 相关阅读:
    博客园是否提供trackback的rss
    firefox的检查gmail的插件Next Generation Extensions!
    windirstat这个软件可以图形化统计磁盘占用,实用!
    笔记本鼠标乱跑!不知何故!
    转贴:说说我的Firefox Extensions
    从hotmail到gmail
    求助:我的网站上出现如下错误,何故?
    关于javscript自动测试的难题!
    手机跳水!
    中国队新主帅朱广沪印象
  • 原文地址:https://www.cnblogs.com/wang-meng/p/9b6c35c4b2ef7e5b398db9211733292d.html
Copyright © 2011-2022 走看看