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. GTK+2.0学习——第一个GTK程序

    #include <gtk/gtk.h> #include <stdio.h> #include <stdlib.h> /* *点击了关闭按钮之后的回调函数 *gt ...

  2. 最近用到mysql和mybatis结合常用的知识点坐下整理

    1.当用到集合in(x,x...)参数可以单个或者多个 ,当为单个时: findbyIds(List<Long> ids),或者findByids(Long [] ids)  <se ...

  3. MyEclipse 中文注释乱码

    window-->preference-->general-->content type然后在<Content Types>中展开每一个子项,并在<Default ...

  4. 【转】Zookeeper-Watcher机制与异步调用原理

    声明:本文转载自http://shift-alt-ctrl.iteye.com/blog/1847320,转载请务必声明. Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作. ...

  5. UNIX基础--进程和守护进程

    进程和守护进程 Processes and Daemons 进程(Processes) FreeBSD 是一个多任务操作系统. 这就意味着好像一次可以运行一个以上的程序. 每个占用一定时间运行的程序就 ...

  6. Registry Workshop(注册表编辑器) V4.6.3 官方中文版

    软件名称: Registry Workshop(注册表编辑器)软件语言: 简体中文授权方式: 免费试用运行环境: Win7 / Vista / Win2003 / WinXP 软件大小: 1.1MB图 ...

  7. 关于C语言知识调查

    因为上一篇随笔对这一部分写得不够清楚,因此在这篇做一些补充. 你是怎么学习C语言的? 起初,对于C语言的学习主要是通过老师课堂的教学,完成相关的课后作业.与我的技能相比的话,他们都有一个共同点需要去实 ...

  8. diff命令

    描述 在最简单的情况是, diff 比较两个文件的内容 (源文件 和 目标文件). 文件名可以是 - 由标准输入设备读入的文本. 作为特别的情况是, diff - - 比较一份标准输入的它自己的拷贝如 ...

  9. mongodb (一)

    #mongodb安装(3.4.0) #下载安装包,解压 mkdir /data/mongodb cd /data/mongodb mkdir log conf data bin vim conf/mo ...

  10. Namespacing in PHP (php 中使用命名空间)

    最近学习一下php的命名空间,中文文档不多,搜到一篇英文的,讲的还蛮系统的,特此翻译一下,以备以后查阅,大家有什么高见或更深刻或者 更悟透的见解希望能不吝赐教,晚辈感激不尽. 原文: http://c ...