在一个良好平衡的查找树中,可以在O(logn)时间内找到一个元素。 使用散列来实现一个映射表或集合,从而在O(1)时间内查找、插入和删除一个元素。
HashMap
java.util.HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
HashMap和Hashtable有什么区别?
- HashMap是Hashtable的轻量级实现(非线程安全的实现),都实现了Map接口,主要区别是HashMap允许key为null(但只允许一条null),而后者不行。
- HashMap把contains方法去掉,改为containsKey()和containsValue()。 Hashtable继承自Dictionary,而HashMap实现了Map接口,继承自AbstractMap。
- Hashtable使用Enumeration遍历,而HashMap使用Iterator遍历。
- 使用的hash/rehash算法几乎一致,性能差别不大
- Hashtable的hash数组默认大小是11,增加方式是2*old + 1。 HashMap中,hash数组默认大小是16,一定是2的指数。
一般在不需要并发的时候使用HashMap,并发的时候使用锁粒度更细的ConcurrentHashMap。
迭代HashMap使用了快速失败机制,fail-fast,是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变操作时,就有可能产生fail-fast事件。假如线程1和线程2,当线程1通过iterator遍历集合A中的元素时,如果线程2修改了集合A的结构(删除、增加新元素),程序就会抛出ConcurrentModificationException异常,从而产生fail-fast事件。
遍历HashMap的四种方法:
keySet需要首先把key转换成itaretor,然后根据key在map中取出value,需要两个操作,而entrySet只一次操作就把key和value都取到entry中来,效率更高。foreach和itaretor是等价的。
- foreach map.entrySet()
1 | public static void traversal(Map<String, String> map) { |
- 显示调用map.entrySet集合迭代器
1 | public static void traversal(Map<String, Integer> map) { |
- foreach map.keySet(), 再调用get方法
本方法多调用一次get,效率会降低,只适合只遍历key的情况。
1 | public static void traversal(Map<String, String> map) { |
- foreach map.entrySet(), 再临时变量保存map.entrySet()
1 | public static void traversal(Map<String, String> map) { |
散列函数和散列码
折叠法
byte、short、int、char类型的搜索键,简单地转换为int。
long类型的散列码: hashCode = (int)(key ^ (key >> 32));
double类型: long bits = Double.doubleToLongBits(key);
int hashCode = (int)(bits ^ (bits >> 32));
除余法(压缩)
假设散列表的索引处于0~N-1 之间。设N为2的幂值。
h(hashCode) = hashCode % N;
h(hashCode) = hashCode & (N - 1);
为了保证散列码是均匀分布的,java.util.HashMap采用了补充的散列函数与主散列函数一起使用。
字符串类型的散列码
(…((s0 * b) + s1)b + s2)b + … + sn-2)b + sn-1
在String中,b取值31,来计算上述多项式,以达到最小化冲突,其中si = s.charAt(i)。
除此之外,还有平方取中法,数字分析法等等。
使用链地址法处理冲突
当两个键映射到散列表中的同一个索引上时,冲突发生。链地址法将具有同样的散列索引的条目都放在同一个位置,而不是寻找一个新位置。链地址法的每个位置使用一个桶来放置多个条目。
可以使用数组,ArrayList或LinkedList来实现一个桶。
使用LinkedListl来实现一个映射表。 此处实现与jdk中的实现不同,只是为了演示理解。
MyMap接口
1 | import java.util.Set; |
MyHashMap类的实现
1 | import java.util.*; |
使用开放地址法处理冲突
开放地址法(open addressing)是在冲突发生时,在散列表中找到一个开放位置的过程。
线性探测法
按照顺序找到下一个可用的位置,如果冲突发生在hashTable[k % N],则检查hashTable[(k+1) % N],依次类推。
查找时,依次检查k,k+1,…,直到达到一个空单元,或者找到。
缺点:会形成一次簇(cluster),从而降低查找时间。
1 | import java.util.ArrayList; |
二次探测法
二次探测法从索引 (k + j*j) % N位置的单元开始审查。
1 | import java.util.ArrayList; |
再哈希法
1 | import java.util.ArrayList; |