`
donlianli
  • 浏览: 340413 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Group-logo
Elasticsearch...
浏览量:218558
社区版块
存档分类
最新评论

谨慎使用String作为HashMap的Key

阅读更多

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

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

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

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

 

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

 

 

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

 

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

 

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

 

String的hashCode方法。

 

public int hashCode() {
	int h = hash;
        int len = count;
	if (h == 0 && len > 0) {
	    int off = offset;
	    char val[] = value;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

 

 

核心的代码就一行。就是

 

  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:计算字符串重复概率的代码

import java.util.HashMap;
/**
 * 测试字符串的hashcode重复几率
 * @author donlianli@126.com
 */
public class StringHashCode {
	
	static HashMap<Integer,Object> map = new HashMap<Integer,Object>(); 
	/**
	 * 第一个可见字符
	 */
    private static char startChar = ' '; 
    /**
     * 最后一个可见字符
     */
    private static char endChar = '~'; 
    private static int offset = endChar - startChar + 1; 
    /**
     * 重复次数
     */
    private static int dupCount = 0; 
    
    public static void main(String[] args) { 
        for(int len=1;len<5;len++){
        	 char[] chars = new char[len]; 
             tryBit(chars, len); 
             int total=(int)Math.pow(offset, len);
             System.out.println(len+":"+total + ":" + dupCount+":"+map.size()+":"+(float)dupCount/total);
        }
        
    } 
 
    private static void tryBit(char[] chars, int i) { 
        for (char j = startChar; j <= endChar; j++) { 
            chars[i - 1] = j; 
            if (i > 1) 
                tryBit(chars, i - 1); 
            else 
                test(chars); 
        } 
    } 
 
    private static void test(char[] chars) { 
    	Integer key = new String(chars).hashCode();
        if (map.containsKey(key)) { 
            dupCount++; 
        } else { 
            map.put(key, null); 
        } 
    } 
}

 

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

 

 

import java.util.HashMap;
/**
 * 测试字符串的hashcode重复几率
 * @author donlianli@126.com
 * 求长度为2的hashcode重复的字符串
 */
public class PrintStringHashCode {
	
	static HashMap<Integer,Object> map = new HashMap<Integer,Object>(); 
	/**
	 * 第一个可见字符
	 */
    private static char startChar = ' '; 
    /**
     * 最后一个可见字符
     */
    private static char endChar = 'z'; 
    private static int offset = endChar - startChar + 1; 
    /**
     * 重复次数
     */
    private static int dupCount = 0; 
    
    public static void main(String[] args) { 
    	int len =2;
		 char[] chars = new char[len]; 
	     tryBit(chars, len); 
	     int total=(int)Math.pow(offset, len);
	     System.out.println(len+":"+total + ":" + dupCount+":"+map.size()+":"+(float)dupCount/total);
    } 
 
    private static void tryBit(char[] chars, int i) { 
        for (char j = startChar; j <= endChar; j++) { 
            chars[i - 1] = j; 
            if (i > 1) 
                tryBit(chars, i - 1); 
            else 
                test(chars); 
        } 
    } 
 
    private static void test(char[] chars) { 
    	String s = new String(chars);
    	Integer key = s.hashCode();
        if (map.containsKey(key)) { 
            dupCount++; 
            System.out.println(map.get(key)+" same :"+s+" hashcode:"+key);
        } else { 
            map.put(key, s); 
        } 
    } 
}

 

 附件3测试UUID的代码:

 public static void testUUID(){
    	int count=1000000;
    	for(int i=0;i<count;i++){
    		String s = UUID.randomUUID().toString(); 
        	Integer key = s.hashCode();
            if (map.containsKey(key)) { 
            	System.out.println(s+":"+map.get(key));
                dupCount++; 
            } else { 
                map.put(key, s); 
            } 
    	}
    	System.out.println( dupCount+":"+map.size()+":"+(float)dupCount/count);
    }

 

 

请支持原创:

http://donlianli.iteye.com/blog/1979674

 

 

 

 

 

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

 

 

1
1
分享到:
评论
3 楼 zui4yi1 2013-11-26  
都忘光了为啥用hash
2 楼 donlianli 2013-11-23  
53873039oycg 写道
数字+英文字符[a-z](总长度>10)作为HashMao的key这种情况下的重复几率如何,有点好奇,因为平时就是这么用的

如果跟UUID想类似,性能应该也没有什么问题,最好还是写个测试代码测试一下。
1 楼 53873039oycg 2013-11-23  
数字+英文字符[a-z](总长度>10)作为HashMao的key这种情况下的重复几率如何,有点好奇,因为平时就是这么用的

相关推荐

    Rust-第8节-常见数据集合vec、string、hashmap

    在实际编程中,了解如何有效地使用Vec、String和HashMap对于编写高性能的Rust程序至关重要。例如,当你需要处理动态大小的数组时,Vec是理想的选择;处理文本数据时,String提供了方便的Unicode支持;而处理键值对的...

    Java用自定义的类作为HashMap的key值实例

    当使用自定义类作为HashMap的键时,为了保证键的唯一性和正确的查找行为,我们需要遵循一些特定规则。这篇文章将深入探讨这个问题,并通过一个具体的实例来解释为什么需要重载`hashCode()`和`equals()`方法。 首先...

    hashmap使用实例

    HashMap是Java编程语言中的一种重要数据结构,它在Android开发中同样被广泛使用。HashMap属于集合框架的一部分,提供了键值对(key-value pair)的存储功能。在这个实例中,我们将深入探讨HashMap的工作原理、基本...

    java 使用web service读取HashMap里的数值

    这两个方法均接受一个`HashMap&lt;String, String&gt;`作为参数,并返回同样的`HashMap`对象。这样的设计使得我们可以轻松地在客户端和服务端之间传递数据。 - **部署与发布**:为了使这个接口能够被远程访问,我们需要将...

    HASHMAP排序功能描述

    - 如果对HashMap进行大量的排序操作,考虑使用TreeMap,它默认按照key的自然顺序排序,也可以自定义Comparator。 **5. 结论** HashMap排序并不是HashMap本身的功能,而是通过其他手段实现的。根据实际需求,可以...

    C++hashmap的使用实例

    在C++编程中,`hashmap`通常指的是`std::unordered_map`,它是一个关联容器,提供了基于哈希表的键值对存储。这个数据结构允许我们以接近常数时间的复杂度进行插入、查找和删除操作,极大地提高了程序的执行效率。...

    Java HashMap类详解

    HashMap 的使用可以通过创建一个 HashMap 对象,然后使用 put 方法将 key-value 对添加到该对象中。例如: Java 代码 HashMap&lt;String, Double&gt; map = new HashMap&lt;String, Double&gt;(); map.put("语文", 80.0); map....

    HashMap排序

    - 使用`TreeMap`:创建一个`TreeMap`对象并传入`ByValueComparator`作为构造函数参数,然后将`HashMap`的所有键值对放入`TreeMap`中。 - 使用`Collections.sort()`:创建一个包含所有键的`ArrayList`,然后调用`...

    A simple string hashmap in C.zip

    标题 "A simple string hashmap in C.zip" 暗示了一个小型的C语言实现的字符串哈希表项目。这个压缩包可能包含一个或多个源代码文件,用于演示如何在C语言中设计并实现一个简单的哈希表,特别是针对字符串键值对的...

    hashMap利用iterator迭代器迭代元素方法

    `HashMap`使用哈希表实现,提供快速的插入、删除和查找操作。当我们需要遍历`HashMap`中的所有元素时,通常会使用`Iterator`接口,它是Java集合框架的一部分,提供了对集合的迭代访问。 `Iterator`接口定义了三个...

    枚举 HashMap

    通过将枚举值作为键(Key),相关属性或行为作为值(Value),我们可以创建一个映射关系,达到类似枚举的效果。 下面我们将详细探讨如何使用HashMap实现枚举功能: 1. **枚举类的替代** 在Java中,枚举类可以定义...

    ArrayList,HashMap

    HashMap&lt;String, Integer&gt; map = new HashMap(); map.put("Key1", 1); map.put("Key2", 2); ``` 为了提高性能,我们需要注意以下几点: 1. 初始化容量:预估ArrayList或HashMap的大小并设置初始容量,可以减少扩容...

    delphi hashmap集合

    - **创建HashMap:** Delphi中,你可以使用TDictionary类来创建一个HashMap实例,例如 `var HashMap: TDictionary&lt;String, Integer&gt;;` - **插入元素:** 使用`.Add`或`.Insert`方法添加键值对,如 `HashMap.Add('...

    StringtoList和StringtoMap和StringtoObject和StringtoArray

    Map&lt;String, String&gt; map = new HashMap(); Iterator&lt;String&gt; keys = jsonObject.keys(); while (keys.hasNext()) { String key = keys.next(); map.put(key, jsonObject.getString(key)); } // 使用Gson ...

    HashMap CRUD操作

    HashMap在内部使用了哈希表的数据结构,通过键值对(Key-Value)的形式存储数据。它的主要特点是查找和插入速度快,平均时间复杂度为O(1)。HashMap不是线程安全的,如果在多线程环境下使用,需要额外的同步控制。 *...

    HashMap遍历

    此外,需要注意的是,`HashMap`并非线程安全的,如果在多线程环境下使用,应考虑同步机制,或使用如`ConcurrentHashMap`等线程安全的替代方案。同时,对于不允许null值的情况,可以选择`LinkedHashMap`或`TreeMap`等...

    java中HashMap详解.pdf

    HashMap&lt;String, Double&gt; map = new HashMap&lt;String, Double&gt;(); map.put("key1", 80.0); map.put("key2", 89.0); map.put("key3", 78.2); ``` 在Java中,当创建一个HashMap时,其底层使用数组来存储数据。键值对...

    电话本管理系统HashMap实现

    Map接口存储键值对(key-value pairs),而HashMap则使用哈希表数据结构来实现,提供平均时间复杂度为O(1)的插入、删除和查找操作。哈希表通过计算对象的哈希码来定位数据,这使得访问速度非常快。 首先,我们需要...

    js 版 java hashmap

    在JavaScript中,可以使用`Object.prototype.hasOwnProperty.call()`方法检查键是否存在,并用`String.prototype.charCodeAt()`获取字符的Unicode编码作为哈希值。 2. **开放寻址法与链地址法**:解决哈希冲突的...

    Hashmap 通过对VALUE排序 源代码

    HashMap是Java编程语言中最常用的集合类之一,它提供了一种基于键值对(key-value pair)的数据存储方式,允许我们通过键快速查找对应的值。在Java的HashMap中,元素是无序的,也就是说,它们在内存中的存储位置并...

Global site tag (gtag.js) - Google Analytics