哈希表(Hash Table,又叫散列表),是存储键值对(Key-value)的表,之所以不叫它Map(键值对一起存储一般叫做Map),是因为它下面的特性:它能把关键码(key)映射到表中的一个位置来直接访问,这样访问速度就非常快。其中的映射函数称为散列函数(Hash function)。

1) 对于关键字key, f(key)是其存储位置,f则是散列函数

2) 如果key1 != key2
但是 f(key1) ==
f(key2),这种现象称为冲突(collison)。冲突不可避免,这是因为key值无限而表容量总是有限(*见篇末思考题*)。我们追求的是对任意关键字,散列到表中的地址概率是相等的,这样的散列函数为均匀散列函数。

散列函数有多种 
×
直接定址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key +
b,其中a和b为常数(这种散列函数叫做自身函数) 
× 数字分析法 
× 平方取中法 
× 折叠法 
× 随机数法 
×
除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,
p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。

可以想像,当表中的数据个数接近表的容量大小时,发生冲突的概率会明显增大,因此,在“数据个数/表容量”到达某个比例的时侯,需要扩大表的容量,这个比例称为“装填因子”(load
factor).

解决冲突主要有下面两类方法: 
× 分离链接法,就是对hash到同一地址的不同元素,用链表连起来,也叫拉链法 
×
开放定址法,如果地址有冲突,就在此地址附近找。包括线性探测法,平方探测法,双散列等

然后来看一下Java的Hashtable实现

java.util.Hashtable的本质是个数组,数组的元素是linked的键值对(单向链表)。

01.private transient Entry[] table; // Entry数组  
01.private static class Entry<K,V> implements Map.Entry<K,V> {
02. int hash;
03. K key;
04. V value;
05. Entry<K,V> next; // Entry此处表明是个单链表
06. ...
07.}

我们可以使用指定数组大小、装填因子的构造函数,也可以使用默认构造函数,默认数组的大小是11,装填因子是0.75.

01.public Hashtable(int initialCapacity, float loadFactor) {
02....
03.}
04.public Hashtable() {
05. this(11, 0.75f);
06.}

当要扩大数组时,大小变为oldCapacity * 2 + 1,当然这无法保证数组的大小总是素数。 
来看下其中的元素插入的方法,put方法:

01.public synchronized V put(K key, V value) {
02. // Make sure the value is not null
03. if (value == null) {
04. throw new NullPointerException();
05. }
06.
07. // Makes sure the key is not already in the hashtable.
08. Entry tab[] = table;
09. int hash = key.hashCode();
10. int index = (hash & 0x7FFFFFFF) % tab.length;
11. for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
12. if ((e.hash == hash) && e.key.equals(key)) {
13. V old = e.value;
14. e.value = value;
15. return old;
16. }
17. }
18.}

Java中Object类有几个方法,其中一个是hashCode(), 这说明Java中所有对象都具有这一方法,调用可以得到对象自身的hash码。对表的长度取余得址,并在冲突位置使用链表。

HashMap与Hashtable的功能几乎一样。但HashMap的的初始数组大小是16而不是11,当要扩大数组时,大小变为原来的2倍,默认的装填因子也是0.75.
其put方法如下,对hash值和index都有更改:

02.    if (key == null)
03. return putForNullKey(value);
04. int hash = hash(key.hashCode());
05. int i = indexFor(hash, table.length);
06. for (Entry<K, V> e = table[i]; e != null; e = e.next) {
07. Object k;
08. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
09. V oldValue = e.value;
10. e.value = value;
11. e.recordAccess(this);
12. return oldValue;
13. }
14. }
15.
16. modCount++;
17. addEntry(hash, key, value, i);
18. return null;
19.}
20.
21.
22./**
23. * Applies a supplemental hash function to a given hashCode, which
24. * defends against poor quality hash functions. This is critical
25. * because HashMap uses power-of-two length hash tables, that
26. * otherwise encounter collisions for hashCodes that do not differ
27. * in lower bits. Note: Null keys always map to hash 0, thus index 0.
28. */
29.static int hash(int h) {
30. // This function ensures that hashCodes that differ only by
31. // constant multiples at each bit position have a bounded
32. // number of collisions (approximately 8 at default load factor).
33. h ^= (h >>> 20) ^ (h >>> 12);
34. return h ^ (h >>> 7) ^ (h >>> 4);
35.}
36.
37./**
38. * Returns index for hash code h.
39. */
40.static int indexFor(int h, int length) {
41. return h & (length-1);
42.}

再看看其它开源的Java库中的Hashtable

目前存在多个开源的Java
Collection实现,各个目的不同,侧重点也不同。以下对开源框架中哈希表的分析主要从几个方面入手:默认装填因子和capacity扩展方式,散列函数以及解决冲突的方法。

1.
Trove - Trove库提供一套高效的基础集合类。

gnu.trove.set.hash.THashMap的继承关系:THashMap
-> TObjectHash ->
THash,其内部的键和值使分别用2个数组表示。其解决冲突的方式采用开放寻址法,开放寻址法对空间要求较高,因此其默认装填因子load
factor是0.5,而不是0.75. 下面看代码一步步解释:

默认初始化,装填因子0.5,数组大小始从素数中取,也就是始终是素数。

01./** the load above which rehashing occurs. */
02.public static final float DEFAULT_LOAD_FACTOR = 0.5f;
03.
04.protected int setUp( int initialCapacity ) {
05. int capacity;
06. capacity = PrimeFinder.nextPrime( initialCapacity );
07. computeMaxSize( capacity );
08. computeNextAutoCompactionAmount( initialCapacity );
09. return capacity;
10.}

然后看其put方法,insertKey(T
key)是其散列算法,hash码对数组长度取余后,得到index,首先检查该位置是否被占用,如果被占用,使用双散列算法解决冲突,也就是代码中的insertKeyRehash()方法。

01.public V put(K key, V value) {
02. // insertKey() inserts the key if a slot if found and returns the index
03. int index = insertKey(key);
04. return doPut(value, index);
05.}
06.
07.
08.protected int insertKey(T key) {
09. consumeFreeSlot = false;
10.
11. if (key == null)
12. return insertKeyForNull();
13.
14. final int hash = hash(key) & 0x7fffffff;
15. int index = hash % _set.length;
16. Object cur = _set[index];
17.
18. if (cur == FREE) {
19. consumeFreeSlot = true;
20. _set[index] = key; // insert value
21. return index; // empty, all done
22. }
23.
24. if (cur == key || equals(key, cur)) {
25. return -index - 1; // already stored
26. }
27.
28. return insertKeyRehash(key, index, hash, cur);
29.}

2. Javolution - 对实时、内置、高性能系统提供Java解决方案

Javolution中的哈希表是jvolution.util.FastMap,
其内部是双向链表,默认初始大小是16,扩展时变为2倍。并没有显式定义load factor, 从下面语句可以知道,其值为0.5

01.if (map._entryCount + map._nullCount > (entries.length >> 1)) { // Table more than half empty.
02. map.resizeTable(_isShared);
03.}

再看下put函数,比较惊人的是其index和slot的取得,完全是用hashkey移位的方式取得的,这样同时计算了index和避免了碰撞。

01.private final Object put(Object key, Object value, int keyHash,
02. boolean concurrent, boolean noReplace, boolean returnEntry) {
03. final FastMap map = getSubMap(keyHash);
04. final Entry[] entries = map._entries; // Atomic.
05. final int mask = entries.length - 1;
06. int slot = -1;
07. for (int i = keyHash >> map._keyShift;; i++) {
08. Entry entry = entries[i & mask];
09. if (entry == null) {
10. slot = slot < 0 ? i & mask : slot;
11. break;
12. } else if (entry == Entry.NULL) {
13. slot = slot < 0 ? i & mask : slot;
14. } else if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key)
15. : _keyComparator.areEqual(key, entry._key)))) {
16. if (noReplace) {
17. return returnEntry ? entry : entry._value;
18. }
19. Object prevValue = entry._value;
20. entry._value = value;
21. return returnEntry ? entry : prevValue;
22. }
23. }
24. ...
25.}

学习记录 java 哈希的更多相关文章

  1. 学习记录-java基础部分(一)

    学习记录-java基础部分(一) 参考:GitHub上的知名项目:javaGuide : https://github.com/Snailclimb/JavaGuide/blob/master/doc ...

  2. 学习记录 java session保存用户登录

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  3. JVM学习记录-Java内存模型(二)

    对于volatile型变量的特殊规则 关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制. 在处理多线程数据竞争问题时,不仅仅是可以使用synchronized关键字来实现,使用vo ...

  4. 学习记录 java 链表知识

    01.import java.util.HashMap; 02.import java.util.Scanner; 03.import java.util.Stack; 04. 05./** 06. ...

  5. 学习记录 java泛型资料

    java泛型资料: 1. 概述在引入范型之前,Java类型分为原始类型.复杂类型,其中复杂类型分为数组和类.引入范型后,一个复杂类型就可以在细分成更多的类型.例如原先的类型List,现在在细分成Lis ...

  6. 学习记录 java 值类型和引用类型的知识

    1. Java中值类型和引用类型的不同? [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变 ...

  7. 学习记录 java随机数的产生机制

    java 随机数 一.在j2se里我们可以使用Math.random()方法来产生一个随机数,这个产生的随机数是0-1之间的一个double,我们可以把他乘以一定的数,比如说乘以100,他就是个100 ...

  8. 学习记录 Java常见的几种字符集以及对 AscII的了解

     1.ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte). ...

  9. JVM学习记录-Java内存模型(一)

    前言 Java虚拟机规范中定义了一种Java的内存模型,即Java Memoory Model(简称JMM),用来实现让Java程序在各个平台下都能达到一致的内存访问效果.JVM是整个虚拟机,JMM模 ...

随机推荐

  1. .NET常用方法收藏

    1.过滤文本中的HTML标签 /// <summary> /// 清除文本中Html的标签 /// </summary> /// <param name="Co ...

  2. 像装软件一样装系统 Win8下怎么装Win7

    像装软件一样装系统 Win8下怎么装Win7 首先,你需要一个Windows7的ISO镜像文件,非ghost版本 一般选中ISO文件,点反键在弹出菜单中以“装载”或“window资源管理器”方式打开 ...

  3. flash builder 启动ios模拟器失败是什么原因?

    参考知乎:http://www.zhihu.com/question/22537362 在mac os设置-安全性与隐私-隐私-辅助功能 找到flash bulder 打上前面的勾,如下图:

  4. 黄聪:WordPress 多站点建站教程(五):获取子站点用户信息(通过输入站点ID号来获取该站点的所有用户)

    得到站点ID为1的用户 <ul> <?php $blogusers = get_users('blog_id=1'); foreach ($blogusers as $user) { ...

  5. DG_Oracle DataGuard Failover主备节点切换(案例)

    2014-03-09 Created By BaoXinjian Thanks and Regards

  6. ERP_基于Oracle SOA的企业服务总线整合

    2015-01-01 Created By BaoXinjian

  7. HDU5221 Occupation 树链剖分

    题意: 给出一棵树,root=1,树有点权,有一个人叫做M 有3种操作: 1 u v 把u到v路径上的所有点的点权都给M 2 u 若u的点权在M手上,拿走 3 u 把u为根的子树的所有点权都给M 每一 ...

  8. C#(结构体_枚举类型)

        结构体一般定义在Main函数上面,位于Class下面,作为一个类:一般情况Struct定义在Main函数前面,Main函数里面的地方都可以使用,参数前面加上public代表公用变量. 用法 1 ...

  9. js让iframe高度自动

    HTML: <iframe id="yb_if" width="940px" src="连接" frameborder=0 allow ...

  10. CentOS 6.5 网络配置(转载)

    From:http://blog.csdn.net/leave00608/article/details/19814063 1.配置网卡IP地址 vim /etc/sysconfig/network- ...