为什么要Hash?
哈希表是基于数组实现的,哈希算法就是如何将键值(key)转换成数组小标的方法,哈希化可以提供非常高的操作(插入、删除、查询)效率,因为对有序数组的查询,即使是二分查找也只能做到O(logN),因为哈希可以直接将要查询的key转化为数组小标,所以可以达到O(1)的时间级。
Hash算法:将key做hash后的值叫hashcode,hashcode的值范围可能很大,Hash算法是将一个较大范围的hashcode转换为定长的区间的数值。一个好的hash算法应该使hashcode均匀分布在区间内。 但是难免还是会有不同的key会产生相同的hashcode,此为哈希冲突。
例如,要对公司100个雇员的信息做哈希存储,要求以姓名作为键值,“frank” 的hashcode是3135416,如果直接用3135416做小标,意味着要用一个非常大的数组,显然非常浪费。mod是最简单,往往也是最有效的hash算法,3135416%100=16,这样就可以用array[16]来存储“frank”的信息了。
如何解决哈希冲突?
- 开放定址法
线性探测,二次探测,再哈希。
例如“leo”的hashcode是733616,用模100哈希算法后,其值也是16,因为array[16]已经被占用了,产生了“冲突”,那么顺序查看16的下个位置17,看array[17]是否可用,如果可以则存储leo,否则继续探测下个位置18,直到有空位为止。在查询时,若一个key的哈希值为16,不能就简单的返回array[16],因为哈希值为16的可能是frank 或者 leo,所以还要比对key值。 - 链地址法
Java 的 HashMap就是用LinkedList解决hash冲突的。
参考:http://en.wikipedia.org/wiki/Hash_function
使用Java HashMap时,注意以下几点:
1. HashMap不是线程安全的
不要在并发环境中使用HashMap,这样会造成不可预期的后果,据sun的说法,在扩容时,会引起链表的闭环,在get元素时,就会无限循环,后果是cpu100%。
如果Concurrent is a must,请用Map m = Collections.synchronizedMap(new HashMap(...))
2.如果数据大小是固定的,那么最好给HashMap设定一个合理的容量值
因为HashMap的默认容量是Capacity(16) * LoadFactor(0.75) = 12,如果你的HashMap是用来装载1万条数据的,那么HashMap会不断的扩容(resize),而每次扩容都会对原有数据做重新排列,非常的time-consuming,所以应该在初始化HashMap的时候用New HashMap(15000),负载因子(loadFactor)用默认的0.75就好了。
More about loadFactor:这个loadFactor有做什么用得呢? 是用来减少collision的,试想一下, 是不是越接近满载的时候,产生冲突的可能性越大呢,就像公交车上有16个座位,此时上来5个人,几乎不会出现抢位子的情况,因为很空。假如又上来10个人,那么15个人对16个座位,可能有些人觉得前面座位比较好,就会产生冲突并问候对方的父母。现在规定这个16座的公交车只允许载客12人,也就是负载因子是0.75,后面来的人用另一辆空车装载,这样就可以有效的解决冲突。
3.成为HashMap的key的条件
HashMap是用一个数组Entry[ ]来存储相应的Entry<K, V>的,所以在put(K, V)操作时,首先检查Key的hashCode,看该Entry在数组中是否已经存在,若不存在,加到数组中,否则,会再将K.equals()于现有已存在数组中得Entry的链表(linked list)中的K进行比较,若有相同的,将其替换, 若没有,将该Entry置于链表的头部。
在get( )操作是,是先查看HashCode,再比较equals,两者皆匹配的时候,才会返回Value。
所以这是为什么要作为HashMap的key要override hashCode 和 equals的原因。
参考:http://www.iteye.com/topic/754887
4. HashMap 和 HashTable, HashSet的比较
发现一个对hash深入研究的博文,以及用hash做Top K 问题的solution (有1000万条(查询串)记录,找出里面top 10出现次数最多的查询串的方法)
http://blog.csdn.net/v_july_v/article/details/6256463
版权声明:本文为博主原创文章,未经博主允许不得转载。