Java 集合 散列表hash table

@author ixenos

摘要:hash table用链表数组实现、解决散列表的冲突:开放地址法 和 链地址法(冲突链表方式)

hash table 是一种数据结构


    hash table 为每个对象计算一个整数,该整数被称为散列码 hash code

    • hash code 是由对象的实例域产生的一个整数,具有不同的数据域的对象将产生不同的hash code
    • 如果自定义类,就要负责实现这个类的hashCode方法,注意要与equals方法兼容,即如果a.equals(b)为true,则a与b的hash code必须相同

Java中hash table用链表数组实现


  • Entry[] table ( HashMap中的key-value都是存储在Entry数组中的 )
  • 桶:bucket,用于收集具有相同hash code的元素。要想查找table中对象的位置,就要先计算它的hash code,然后与bucket的总数取余,得到的就是保存这个元素的bucket的index

           //图片来自《Core Java》

    • 如果bucket中没有其他元素,此时将元素直接插入bucket中就可以了;如果bucket中有元素,需要用新对象与该bucket中所有的对象进行比较,查看这个对象是否已经存在,不存在则修改链表结点索引加入bucket;如果bucket被占满,此现象被称为散列冲突hash collision,此时需要用新对象与该bucket中所有的对象进行比较,查看这个对象是否已经存在
  • 散列冲突:hash collision,如果插入到HashTable中的元素太多,就会增加hash collision的可能性,降低性能,所以要指定一个初始的桶数。通常,将桶数设置为预计元素个数的75%~150%;不同key是可以同hash的,然后就加入桶中的双链表,由于链表访问效率低,所以尽量避免hash冲突
    • 【API文档】
    •   public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, Serializable

      此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。Hashtable 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。

  •  再散列:rehashed,如果预估过低,HashTable太满,就需要再散列 rehashed 如果装载因子为0.75,而表中超过75%的位置已经填入元素,这个HashTable就会用双倍的桶数再散列rehashed

解决散列表的冲突:开放地址法 和 链地址法(冲突链表方式)


  • 根据对冲突的处理方式不同,散列表有两种实现方式:一种开放地址方式(Open addressing),另一种是冲突链表方式(Separate chaining with linked lists)。Java HashMap采用的是冲突链表方式。
  • 开放地址法:这个方法的基本思想是:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。

    • 这个过程可用下式描述: H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))

    • 其中: H ( key ) 为关键字 key 的直接哈希地址, m 为哈希表的长度, di 为每次再探测时的地址增量。

    • 采用这种方法时,首先计算出元素的直接哈希地址 H ( key ) ,如果该存储单元已被其他元素占用,则继续查看地址为 H ( key ) + d 2 的存储单元,如此重复直至找到某个存储单元为空时,将关键字为 key 的数据元素存放到该单元。

      • 增量 d 可以有不同的取法,并根据其取法有不同的称呼:

        • ( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列;
        • ( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列;
        • ( 3 ) d i = 伪随机序列 伪随机再散列;
        • ( 1 )线性探测再散列:
          32 % 7 = 4 ; 13 % 7 = 6 ; 49 % 7 = 0 ;
          55 % 7 = 6 发生冲突,下一个存储地址( 6 + 1 )% 7 = 0 ,仍然发生冲突,再下一个存储地址:( 6 + 2 )% 7 = 1 未发生冲突,可以存入。
          22 % 7 = 1 发生冲突,下一个存储地址是:( 1 + 1 )% 7 = 2 未发生冲突;
          38 % 7 = 3 ;
          21 % 7 = 0 发生冲突,按照上面方法继续探测直至空间 5 ,不发生冲突,所得到的哈希表对应存储位置:
          下标: 0 1 2 3 4 5 6
          49 55 22 38 32 21 13
          ( 2 )二次探测再散列:
          下标: 0 1 2 3 4 5 6
          49 22 21 38 32 55 13

          例子

    • 注意:对于利用开放地址法处理冲突所产生的哈希表中删除一个元素时需要谨慎,不能直接地删除,因为这样将会截断其他具有相同哈希地址的元素的查找地址,所以,通常采用设定一个特殊的标志以示该元素已被删除。

  • 链地址法(HashMap采用的方法)

    • 链地址法解决冲突的做法是:如果散列表空间为 0 ~ m - 1 ,设置一个由 m 个指针分量组成的一维数组 ST[ m ], 凡散列地址为 i 的数据元素都插入到头指针为 ST[ i ] 的链表中。这种方法有点近似于邻接表的基本思想,且这种方法适合于冲突比较严重的情况

hash table可以用于实现几个重要的数据结构


HashSet内部维护了一个HashMap<E,Object>对象用以存储set对象,屏蔽了map中的键,大部分方法的实现都借助了HashMap的方法。

  • public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{
    
        private transient HashMap<E,Object> map;
    ...
    public HashSet() { map = new HashMap<>(); }
    ...
    }

比如contains方法的实现

  • //HashSet的contains方法源码(借助HashMap的方法)
    public boolean contains(Object o) {
    return map.containsKey(o);
    } //来自HashMap的源码
    final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
    (first = tab[(n - 1) & hash]) != null) {
    if (first.hash == hash && // always check first node
    ((k = first.key) == key || (key != null && key.equals(k))))
    return first;
    if ((e = first.next) != null) {
    if (first instanceof TreeNode)
    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
    do {
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    return e;
    } while ((e = e.next) != null);
    }
    }
    return null;
    } public boolean containsKey(Object key) { //被HashSet的contains方法调用
    return getNode(hash(key), key) != null;
    }
  • 从源码中可以看出:contains方法能用来快速查看是否某个元素已经出现在集中,因为它只在某个桶中查找元素,而不必查看集合中的所有元素
  • HashSet迭代器将依次访问所有的bucket,由于hash将元素分散在表的各个位置上(只有不关心集合中元素的顺序时才应该使用HashSet),所以访问他们的顺序几乎是随机的(当然这随机是"固定"了的)

由于元素的hash code决定所在bucket,因此修改集set中元素的内部特征(实例域)的时候,要小心hash code码改变导致元素在数据结构中的位置的变化

Java 集合 散列表hash table的更多相关文章

  1. [转载] 散列表(Hash Table)从理论到实用(中)

    转载自:白话算法(6) 散列表(Hash Table)从理论到实用(中) 不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好 ...

  2. [转载] 散列表(Hash Table) 从理论到实用(下)

    转载自: 白话算法(6) 散列表(Hash Table) 从理论到实用(下) [澈丹,我想要个钻戒.][小北,等等吧,等我再修行两年,你把我烧了,舍利子比钻戒值钱.] ——自扯自蛋 无论开发一个程序还 ...

  3. [转载] 散列表(Hash Table)从理论到实用(上)

    转载自:白话算法(6) 散列表(Hash Table)从理论到实用(上) 处理实际问题的一般数学方法是,首先提炼出问题的本质元素,然后把它看作一个比现实无限宽广的可能性系统,这个系统中的实质关系可以通 ...

  4. 散列表(Hash table)及其构造

    散列表(Hash table) 散列表,是根据关键码值(Key value)而直接进行访问的数据结构.它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录 ...

  5. 散列表(Hash Table)

    散列表(hash table): 也称为哈希表. 根据wikipedia的定义:是根据关键字(Key value)而直接访问在内存存储位置的数据结构.也就是说,它通过把键值通过一个函数的计算,映射到表 ...

  6. 散列表(hash table)——算法导论(13)

    1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...

  7. 算法导论-散列表(Hash Table)-大量数据快速查找算法

    目录 引言 直接寻址 散列寻址 散列函数 除法散列 乘法散列 全域散列 完全散列 碰撞处理方法 链表法 开放寻址法 线性探查 二次探查 双重散列 随机散列 再散列问题 完整源码(C++) 参考资料 内 ...

  8. 白话算法(6) 散列表(Hash Table)从理论到实用(中)

    不用链接法,还有别的方法能处理碰撞吗?扪心自问,我不敢问这个问题.链接法如此的自然.直接,以至于我不敢相信还有别的(甚至是更好的)方法.推动科技进步的人,永远是那些敢于问出比外行更天真.更外行的问题, ...

  9. 白话算法(6) 散列表(Hash Table)从理论到实用(上)

    处理实际问题的一般数学方法是,首先提炼出问题的本质元素,然后把它看作一个比现实无限宽广的可能性系统,这个系统中的实质关系可以通过一般化的推理来论证理解,并可归纳成一般公式,而这个一般公式适用于任何特殊 ...

随机推荐

  1. http协议的状态码 403 404 301 302 200 500 502 504 报错显示

    在网站建设的实际应用中,容易出现很多小小的失误,就像MySQL当初优化不到位,影响整体网站的浏览效果一样,其实,网站的常规http状态码的表现也是一样,Google无法验证网站几种解决办法,提及到由于 ...

  2. Unity3d请求webservice

    我们在对接第三方sdk时,第三方sdk通常会以一个webservice接口的形式供我们来调用.而这些接口会以提供我们get,post,soap等协议来进行访问.get,post方法相信大家都比较熟悉了 ...

  3. 将图片转换为base64 格式

    1.页面上的图片,转换成base64格式,可以通过canvas 的 toDataURL 例子:给定图片的url 将图片转换为base64 var imageSrc = "../images/ ...

  4. 效果网址http://sc.chinaz.com/tag_jiaoben/tupianlunbo.html

    http://sc.chinaz.com/tag_jiaoben/tupianlunbo.html

  5. 用maven在eclipse用spring建javaweb工程(一)

    一,步骤   只标注重点步骤: 新建工程选择Maven Project next---next---选择web工程 next---命名如下: 点击finish创建完成. 二,index.jsp和pom ...

  6. umount: /home: device is busy

    转自:umount: /home: device is busy 取消挂载/home时出现umount: /home: device is busy,原因是因为有程序在使用/home目录,我们可以使用 ...

  7. CentOS 7将Python 2.X.X升级到Python 3.X.X

    # cd /usr/local/src/ # ls Python-.tgz # .tgz # ls Python- Python-.tgz # cd Python-/ # yum install gc ...

  8. how to check if you have TURNIN successfully?

    For example, if I want to check if I have turnin my proj1 of cs130a successfully, I just use this co ...

  9. rune is alias of int32

    I think chendesheng's quote gets at the root cause best: Go uses a lot of signed values, not just fo ...

  10. SQLServer中的变量:局部变量,全局变量

    SQLServer中的全局变量   变量 Transact-SQL语言中有两种形式的变量,一种是用户自己定义的局部变量,另外一种是系统提供的全局变量.局部变量 局部变量是一个能够拥有特定数据类型的对象 ...