谨慎使用String作为HashMap的Key

来源:互联网 发布:php log日志 linux 编辑:程序博客网 时间:2024/06/05 14:19

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

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

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

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

 

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

 

 

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

 

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

 

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

 

String的hashCode方法。

 

Java代码  收藏代码
  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、其他唯一主键情况。

 

我对UUID进行了测试(SYS_GUID和md5跟UUID的拼接都类似,都是字母+数字)。1万个字符串,发现并没有重复的hashcode,1千万的时候,也就重复了117个,这是怎么回事呢?

 

有一种猜测是这样的,虽然UUID的跨度为75,但是随着字符串的长度的增长(UUID为36,包括中划线),概率会逐渐降低。

还有一种猜测,就是UUID只去了75个字符组成的字符串的一部分,大大降低了hashcode重复的概率。

 

因此,对于以上类型的key,几乎不用担心重复的概率,但是如果你的字符串如果真的是随机的可见字符的话,那你可以看仔细了。当心你的hashMap变成List。

 

 

 

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

Java代码  收藏代码
  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的代码

 

 

Java代码  收藏代码
  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. }  

 

 附件3测试UUID的代码:

Java代码  收藏代码
  1. public static void testUUID(){  
  2.     int count=1000000;  
  3.     for(int i=0;i<count;i++){  
  4.         String s = UUID.randomUUID().toString();   
  5.         Integer key = s.hashCode();  
  6.            if (map.containsKey(key)) {   
  7.             System.out.println(s+":"+map.get(key));  
  8.                dupCount++;   
  9.            } else {   
  10.                map.put(key, s);   
  11.            }   
  12.     }  
  13.     System.out.println( dupCount+":"+map.size()+":"+(float)dupCount/count);  
  14.    }  

 

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小腿长疙瘩很痒怎么办 腿过敏起红疙瘩怎么办 肚子上起红疙瘩很痒怎么办 小蚂蚁咬了肿了怎么办 锦鲤鱼尾巴烂了怎么办 泰迪身上长白毛怎么办 鱼身上有红斑了怎么办 新买锦鲤不吃食怎么办 鱼身上有红血丝怎么办 大腿内侧有红色条纹怎么办 腿上出现红血丝怎么办 孕妇有脚气,很痒怎么办 孕晚期脚气很痒怎么办 孕期有脚气很痒怎么办 激光后留下色沉怎么办 腋下很黑怎么办怎样才能变白 屁股上长一块癣怎么办 鼻两侧一热发红怎么办 脸上起皮发红痒怎么办 自癜风发红发痒怎么办 脸又干又痒怎么办 脸发红还有点痛怎么办 脸过敏了红痒怎么办 脸两边一片红痒怎么办 婴儿大腿内侧破皮怎么办 宝宝大腿根淹了怎么办 大腿一走路就疼怎么办 下面痒怎么办用什么洗 长藓怎么办用什么药膏 小腿长湿疹很痒怎么办 产后排不出大便怎么办 3岁宝宝大便不通怎么办 腿上干燥像鱼鳞怎么办 一岁脸上长癣怎么办 脖子长了一片癣怎么办 深圳摇到车牌后怎么办 发现车被套牌了怎么办 我车牌被套牌了怎么办 车子被别人套牌怎么办 车被别人套牌了怎么办 被套牌了有违章怎么办