Quantcast
Channel: CSDN博客推荐文章
Viewing all articles
Browse latest Browse all 35570

谨慎使用String作为HashMap的Key

$
0
0

首先简单复习一下哈希表知识(大学课本定义)。

        根据设定的哈希函数f(key)和处理冲突的方法将一组关键字映像到一个有限的连续地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为哈希表。

         哈希函数f(key)是一个映像,使得任何关键字由此所得到的哈希函数值都落在表允许范围之内。

         对不同的关键字可能得到同一哈希地址,即key!=key2,但是f(key1)=f(key2),这种现象称为冲突。一般情况下,冲突只能减少,而不能完全避免。

 

还不清楚?请百科普及一下吧。

 

 

通过上面的复习,我们知道,决定一个哈希表的性能主要是哈希表的键值的冲突概率。如果哈希后的冲突很低,性能就高,相反,性能则低。使用一个好的哈希算法,可以降低哈希冲突的概率,提高命中率。

 

但是,如果被哈希的Key本身就是重复的,那么哈希算法再好,也无法避免哈希值的冲突。

 

我们都知道,在Java中,HashMap一般是使用对象的hashcode作为哈希的Key的。那么使用String作为HashMap的Key,好不好呢?或者,你在不知情的情况一下,已经干过很多次了。

 

String的hashCode方法。

 


  1. public int hashCode() {  
  2.     int h = hash;  
  3.         int len = count;  
  4.     if (h == 0 && len > 0) {  
  5.         int off = offset;  
  6.         char val[] = value;  
  7.   
  8.             for (int i = 0; i < len; i++) {  
  9.                 h = 31*h + val[off++];  
  10.             }  
  11.             hash = h;  
  12.         }  
  13.         return h;  
  14.     }  

 

 

核心的代码就一行。就是

 

Java代码  收藏代码
  1. h = 31*h + val[off++];  

 他的意思就是一个字符串的hashcode,就是逐个按照字符的utf-16的编码值求和。

 

我个人觉得,像这样的计算hashcode的话,各个字符串很容易重复(虽然我数学不好)。比如:"C9"和“Aw”

 的hashcode都是2134。这样的长度为2位的字符串,我用程序统计了一下,重复的概率大概是0.6665928。

 

当字符长度为3个字符时,重复的概率成上升趋势,达到0.8911293,4位时为0.9739272。当然,5位长度的概率我不知道,因为我的机器上跑不出来结果。

测试代码见附1。

 

这么高的重复率,如果你使用它作为hashcode的话,势必会造成很大的哈希冲突,从而降低哈希表最初的设计初衷,性能降低。

 

但是,那String设计的时候,为啥这样设计hashcode呢?我经过测试,当字符串仅为数字时,多长的字符串,hashcode都不会重复。这是为什么呢?

 

从他计算的公式的31的系数看,应该是31为一个跨度,即只要字符串中的字符串的跨度在31个之内,hash值就不会重复,经过测试,确实如此。也就是说,如果你使用纯英文大写或纯英文小写字母拼接起来的字符串,其hashcode一般不会重复的。不知道这个31最初是怎么算出来的,但是,毋庸置疑,我们可以通过重新String的hashcode方法,将31改为128,那么冲突就会大大降低。

 

看看可能会作为Key的情况。

1、MD5,一般是字母加数字,字符跨度为75.

2、oracle的sys_guid()产生的逐渐,字符跨度为43.

3、java的UUID,跨度为75.

4、其他唯一主键情况。

 

建议,如果你的对象主键是上述类型,则尽量少的使用HashMap作为进行运算的工具类。

 

因此,当你打算使用String作为HashMap的Key时,我建议两点:

1、如果你不知道你的Key的可能的取值范围是否超过31,并且不知数量是多大时,尽量不要使用。

2、如果你对性能要求很高,请尽量不要将字符串作为主键。

 

 

附1:计算字符串重复概率的代码


  1. import java.util.HashMap;  
  2. /** 
  3.  * 测试字符串的hashcode重复几率 
  4.  * @author donlianli@126.com 
  5.  */  
  6. public class StringHashCode {  
  7.       
  8.     static HashMap<Integer,Object> map = new HashMap<Integer,Object>();   
  9.     /** 
  10.      * 第一个可见字符 
  11.      */  
  12.     private static char startChar = ' ';   
  13.     /** 
  14.      * 最后一个可见字符 
  15.      */  
  16.     private static char endChar = '~';   
  17.     private static int offset = endChar - startChar + 1;   
  18.     /** 
  19.      * 重复次数 
  20.      */  
  21.     private static int dupCount = 0;   
  22.       
  23.     public static void main(String[] args) {   
  24.         for(int len=1;len<5;len++){  
  25.              char[] chars = new char[len];   
  26.              tryBit(chars, len);   
  27.              int total=(int)Math.pow(offset, len);  
  28.              System.out.println(len+":"+total + ":" + dupCount+":"+map.size()+":"+(float)dupCount/total);  
  29.         }  
  30.           
  31.     }   
  32.    
  33.     private static void tryBit(char[] chars, int i) {   
  34.         for (char j = startChar; j <= endChar; j++) {   
  35.             chars[i - 1] = j;   
  36.             if (i > 1)   
  37.                 tryBit(chars, i - 1);   
  38.             else   
  39.                 test(chars);   
  40.         }   
  41.     }   
  42.    
  43.     private static void test(char[] chars) {   
  44.         Integer key = new String(chars).hashCode();  
  45.         if (map.containsKey(key)) {   
  46.             dupCount++;   
  47.         } else {   
  48.             map.put(key, null);   
  49.         }   
  50.     }   
  51. }  

 

附2:计算字符串为长度为2的重复hashcode的代码

 

 


  1. import java.util.HashMap;  
  2. /** 
  3.  * 测试字符串的hashcode重复几率 
  4.  * @author donlianli@126.com 
  5.  * 求长度为2的hashcode重复的字符串 
  6.  */  
  7. public class PrintStringHashCode {  
  8.       
  9.     static HashMap<Integer,Object> map = new HashMap<Integer,Object>();   
  10.     /** 
  11.      * 第一个可见字符 
  12.      */  
  13.     private static char startChar = ' ';   
  14.     /** 
  15.      * 最后一个可见字符 
  16.      */  
  17.     private static char endChar = 'z';   
  18.     private static int offset = endChar - startChar + 1;   
  19.     /** 
  20.      * 重复次数 
  21.      */  
  22.     private static int dupCount = 0;   
  23.       
  24.     public static void main(String[] args) {   
  25.         int len =2;  
  26.          char[] chars = new char[len];   
  27.          tryBit(chars, len);   
  28.          int total=(int)Math.pow(offset, len);  
  29.          System.out.println(len+":"+total + ":" + dupCount+":"+map.size()+":"+(float)dupCount/total);  
  30.     }   
  31.    
  32.     private static void tryBit(char[] chars, int i) {   
  33.         for (char j = startChar; j <= endChar; j++) {   
  34.             chars[i - 1] = j;   
  35.             if (i > 1)   
  36.                 tryBit(chars, i - 1);   
  37.             else   
  38.                 test(chars);   
  39.         }   
  40.     }   
  41.    
  42.     private static void test(char[] chars) {   
  43.         String s = new String(chars);  
  44.         Integer key = s.hashCode();  
  45.         if (map.containsKey(key)) {   
  46.             dupCount++;   
  47.             System.out.println(map.get(key)+" same :"+s+" hashcode:"+key);  
  48.         } else {   
  49.             map.put(key, s);   
  50.         }   
  51.     }   
  52. }  

 

 

 

 

 

 

 

对这类话题感兴趣?欢迎发送邮件至donlianli@126.com
关于我:邯郸人,擅长Java,Javascript,Extjs,oracle sql。
更多我之前的文章,可以访问 我的空间

 

作者:donlian 发表于2013-11-22 23:23:51 原文链接
阅读:68 评论:0 查看评论

Viewing all articles
Browse latest Browse all 35570

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>