A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 刘宏庆 黑马帝   /  2011-7-25 13:06  /  4790 人查看  /  4 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

昨天闲来无事看HashMap的源代码,看到这里hash()方法,自己都搞不懂为什么是这个算法,有哪位大侠指点指点,谢谢了

Java代码[code=java]1.static int hash(int h) {   
2.        // This function ensures that hashCodes that differ only by   
3.        // constant multiples at each bit position have a bounded   
4.        // number of collisions (approximately 8 at default load factor).   
5.        h ^= (h >>> 20) ^ (h >>> 12);   
6.        return h ^ (h >>> 7) ^ (h >>> 4);   
7.    }  [/code]

4 个回复

倒序浏览
这个就是hash算法,对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 Hash 码值总是相同的。
1、>>>无符号右移运算符,相对于>>而言,把第一个操作数的二进制码右移指定的位数后,左边空出来的位以原来的符号位来填充,即原来第一个操作数是正数,则左边补0;如果是负则补1;>>>是没有符号的运算符,在把一个二进制码右移之后,左边空的以0来填充
如:[code=java]System.out.println(-5 >> 2);//输出-2
System.out.println(-5 >>> 2);//输出1073741822[/code]--------------------------------------------------------------
比如对于如下的例子让hashMap存储多个数据[code=java]HashMap<String , Double> map = new HashMap<String , Double>();
map.put("数学" , 60.0);
map.put("英语" , 25.0);
map.put("地理" , 12.2); [/code]HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。
当程序执行  map.put("数学" , 60.0);  时,系统将调用"数学"的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。

HashMap 类的 put(K key , V value) 方法的源代码[code=java]public V put(K key, V value)
{
         // 如果 key 为 null,调用 putForNullKey 方法进行处理
         if (key == null)
                 return putForNullKey(value);
         // 根据 key 的 keyCode 计算 Hash 值
         int hash = hash(key.hashCode());
         // 搜索指定 hash 值在对应 table 中的索引
         int i = indexFor(hash, table.length);
         // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素
         for (Entry<K,V> e = table; e != null; e = e.next)
         {
                 Object k;
                 // 找到指定 key 与需要放入的 key 相等(hash 值相同
                 // 通过 equals 比较放回 true)
                 if (e.hash == hash && ((k = e.key) == key
                         || key.equals(k)))
                 {
                         V oldValue = e.value;
                         e.value = value;
                         e.recordAccess(this);
                         return oldValue;
                 }
         }
         // 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry
         modCount++;
         // 将 key、value 添加到 i 索引处
         addEntry(hash, key, value, i);
         return null;
} [/code]Map.Entry为hashMap的内部接口,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。
该方法提供了一个根据 hashCode() 返回值来计算 Hash 码的方法,也就是楼主提出的代码[code=java]static int hash(int h)
{
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
} [/code]这个方法是一个纯粹的数学计算,对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 Hash 码值总是相同的
[ 本帖最后由 詹季春 于 2011-07-25  13:30 编辑 ]
回复 使用道具 举报
黑马网友  发表于 2011-7-25 13:15:52
藤椅
这是我在网上找的希望对搂住有所帮助
这个问题有点难度,不是很好说清楚。

我来做一个比喻吧。
我们有很多的小猪,每个的体重都不一样,假设体重分布比较平均(我们考虑到公斤级别),我们按照体重来分,划分成100个小猪圈。
然后把每个小猪,按照体重赶进各自的猪圈里,记录档案。

好了,如果我们要找某个小猪怎么办呢?我们需要每个猪圈,每个小猪的比对吗?
当然不需要了。

我们先看看要找的这个小猪的体重,然后就找到了对应的猪圈了。
在这个猪圈里的小猪的数量就相对很少了。
我们在这个猪圈里就可以相对快的找到我们要找到的那个小猪了。

对应于hash算法。
就是按照hashcode分配不同的猪圈,将hashcode相同的猪放到一个猪圈里。
查找的时候,先找到hashcode对应的猪圈,然后在逐个比较里面的小猪。

所以问题的关键就是建造多少个猪圈比较合适。

如果每个小猪的体重全部不同(考虑到毫克级别),每个都建一个猪圈,那么我们可以最快速度的找到这头猪。缺点就是,建造那么多猪圈的费用有点太高了。

如果我们按照10公斤级别进行划分,那么建造的猪圈只有几个吧,那么每个圈里的小猪就很多了。我们虽然可以很快的找到猪圈,但从这个猪圈里逐个确定那头小猪也是很累的。

所以,好的hashcode,可以根据实际情况,根据具体的需求,在时间成本(更多的猪圈,更快的速度)和空间本(更少的猪圈,更低的空间需求)之间平衡。
回复 使用道具 举报
黑马网友  发表于 2011-7-25 13:30:51
板凳

回复 藤椅 的帖子

不错,不错,
回复 使用道具 举报
黑马网友  发表于 2011-7-25 14:07:11
报纸
<<  左移
>>  右移
>>> 无符号右移  我理解的>>移位后要保留符号,>>>则不会保留。
举个例子>>与>>>的区别[code]public class ShiftTest
{
public static void main(String [] args)
{
                int x=0x80000000;
                int y=0x80000000;
x=x>>1;
y=y>>>1;
System.out.println(“0x80000000>>1 = ” + Integer.toHexString(x));
System.out.println(“0x80000000>>>1 = ” + Integer.toHexString(y));
}
}[/code]运行结果如下:
0x80000000>>1 = c0000000
0x80000000>>>1 = 40000000

^运算符的例子如下:[code]public class YiHuo
{
public static void main(String [] args)
{
                int x=0x80000000;
                int y=0x40000000;

int z=x^y;
System.out.println("x^y   " + Integer.toHexString(z));

}
}[/code]运行结果如下:
x^y  c0000000
希望对你有帮助。
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马