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. Android学习笔记(二)Git和Github

    一.添加SSH Key ssh-keygen -t rsa -C "email@example.com" 遇到提示只需要一直确认.用户目录(如/root)下会生成一个.ssh文件夹 ...

  2. Android Studio利用异步任务AsyncTask发送post请求获取json数据

    syncTask,是Android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主 ...

  3. 图片,音频资源预加载和文档dom加载

    在项目中遇到一个问题,ajax请求音频资源,然后动态的插入到文档中,其中.原生的音频外观实在太丑了,而且每个浏览器的样式都不一样,采取了一个audio插件. 就遇到一个问题,请求后的直接调用插件的话, ...

  4. json字符串参数

    jsp部分        json字符串的属性应该都是实体类的属性 function saveCashier(){ layer.closeAll(); var Reapply = document.g ...

  5. Mysql之CentOS初探

    1. 卸载mysql 查看CentOS是否已经安装mysql数据库 rpm -qa | grep mysqlrpm -qa | grep MySQL 如果有,则卸载 // --nodeps表示强制rp ...

  6. ECMAScript6之数值类型的扩展

    数值类型的扩展 Number.isNaN ES6将isNaN方法从window身上移植到了Number对象上,使用时和ES5中的isNaN方法一样,但是这是Number对象的方法 Number.isN ...

  7. FaceBook页面加载技术

    1. 技术背景 FaceBook页面加载技术 试想这样一个场景,一个经常访问的网站,每次打开它的页面都要要花费6 秒:同时另外一个网站提供了相似的服务,但响应时间只需3 秒,那么你会如何选择呢?数据表 ...

  8. RSA算法记录----摘抄

    RSA算法原理(一)   "公钥加密算法". 因为它是计算机通信安全的基石,保证了加密数据不会被破解.你可以想象一下,信用卡交易被破解的后果. 进入正题之前,我先简单介绍一下,什么 ...

  9. mysql随记

    .frm是描述了表的结构,.MYD保存了表的数据记录,*.MYI则是表的索引 ibd是MySQL数据文件.索引文件,无法直接读取. ibdata是innodb引擎使用的 如果是使用myisam引擎 则 ...

  10. 跳转到设置页面 与appstory

    //MARK:系统跳到应用设置页面 func systemMySet(){ let url = NSURL(string: UIApplicationOpenSettingsURLString) if ...