散列表

散列表(hash table)为每个对象计算一个整数,称为散列码(hash code)。 若需要自定义类,就要负责实现这个类的hashCode方法。注意自己实现的hashCode方法应该与equals方法兼容,即如果a.equals(b)为true,a与b必须具有相同的散列码。

hashCode方法

散列码是由对象导出的一个整型值,散列码是没有规律的,即若x与y是两个不同的对象,二者的散列码基本不会相同。 String类用下列算法计算散列码:

int hash = 0;
for (int i = 0; i < length(); i++)
hash = 31 * hash + charAt(i);

由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。 hashCode方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,一边能够让各个不同的对象产生的散列码更均匀。 需要组合多个散列值时,可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用Objects.hashCode,并组合这些散列值。

static int hash(Object... Objects)
返回一个散列码,由提供的所有对象的散列码组合而得到。

散列码应该能够快速地计算出来,并且这个计算只与要散列的对象状态有关,与散列表中其它对象无关。

Java中的散列表实现

Java中散列表用链表数组实现,每个列表被称为桶(bucket)。要想查找table中对象的位置,就要先计算它的散列码,然后与桶的总数取余,得到的就是保存这个元素的桶的索引。 如果bucket中没有其他元素,此时将元素直接插入bucket中就可以了;如果bucket中有元素,需要用新对象与该bucket中所有的对象进行比较,查看这个对象是否已经存在,不存在则修改链表结点索引加入bucket;如果bucket被占满,此现象被称为散列冲突(hash collision),此时需要用新对象与该bucket中所有的对象进行比较,查看这个对象是否已经存在。

桶数设置

若想更多地控制散列表的运行性能,就要指定一个初始的桶数。桶数是指用于收集具有相同散列值的桶的数目。 如果大致知道最终会有多少个元素要插入到散列表中,就可以设置桶数。通常将桶数设置为预计元素个数的75% ~ 150%。 有些研究人员认为,最好将桶数设置为一个素数,以防键的集聚。

设有哈希H(c) = c % N 取N为合数N = 2 ^ 3 = 8。 H(11100)= H(36)= 4; H(10100)= H(28)= 4; c的二进制第四位不参与运算,即无论取何值都不影响计算结果。 这样H(c)无法完整地反映c的特性,增大导致冲突的几率。

此外,实际中往往关键字有某种规律,例如大量的等差数列,那么公差和模数不互质的时候发生碰撞的概率会变大,而用质数可以在很大程度上回避这个问题,基本可以保证c的每一位都参与c的运算,从而在常见应用中减少冲突。

若散列表太慢,就需要再散列(rehashed)。需要创建一个桶数更多的表,并将所有元素插入到这个新表中,然后丢弃原来的表。装填因子决定何时对散列表进行再散列。一般0.75比较合理,即表中超过75%的位置已经填入元素时,这个表就会用双倍的桶数自动地进行再散列。

HashMap在根据用户传入的capacity计算得到默认容量,并不考虑load factor的因素,而是直接计算出第一个大于这个数字的2的幂。

设置默认容量可以参考JDK8中putAll的实现,即若明确知道HashMap中元素的个数,计算expectedSize / 0.75F + 1.0F是一个在性能上相对比较好的选择,但同时也会牺牲部分内存。

HashMap中使用HashMap(int initialCapacity)来实现。

参考:https://mp.weixin.qq.com/s/SFss68LcQc5ZFGpu-Ssgog

散列冲突的解决方法

  1. 开放地址法 当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。 公式:Hi=(H(key)+di) MOD m i=1,2,…,k (k <= m 1), H(key)为key的直接哈希地址,m为哈希表的长度,di为每次再探测时的地址增量。 增量di可以有不同的取法,并根据其取法有不同的称呼: ( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列; ( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列; ( 3 ) d i = 伪随机序列 伪随机再散列; 注意:对于利用开放地址法处理冲突所产生的哈希表中删除一个元素时需要谨慎,不能直接地删除,因为这样将会截断其他具有相同哈希地址的元素的查找地址,所以,通常采用设定一个特殊的标志以示该元素已被删除。

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

  3. 再哈希法 当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突。 缺点:计算时间增加。

HashTable实现的数据结构

散列表可以用于实现几个重要的数据结构,其中最简单的是set类型。 Java集合类库中提供了一个HashSet类。散列集迭代器将依次访问所有的桶,由于散列将各个元素分散在表的各个位置上,所以访问它们的顺序几乎是随机的。

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;
}

参考: 《Core Java》

为什么一般hashtable的桶数会取一个素数

Java 集合 散列表hash table

Java 散列集笔记的更多相关文章

  1. Java学习笔记(2)----散列集/线性表/队列/集合/图(Set,List,Queue,Collection,Map)

    1. Java集合框架中的所有实例类都实现了Cloneable和Seriablizable接口.所以,它们的实例都是可复制和可序列化的. 2. 规则集存储的是不重复的元素.若要在集合中存储重复的元素, ...

  2. java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列

    java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列 package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象 ...

  3. java 散列

    原文:https://www.cnblogs.com/younghao/p/8333795.html 为什么要设计散列这种数据结构呢?在现实世界中,实体之间可能存在着映射关系(key-value),比 ...

  4. java 散列运算浅分析 hash()

            文章部分代码图片和总结来自参考资料 哈希和常用的方法 散列,从中文字面意思就很好理解了,分散排列,我们知道数组地址空间连续,查找快,增删慢,而链表,查找慢,增删快,两者结合起来形成散列 ...

  5. Java散列和散列码的实现

    转自:https://blog.csdn.net/al_assad/article/details/52989525 散列和散列码   ※正确的equals方法应该满足的的条件: ①自反性:x.equ ...

  6. 数据结构与算法分析java——散列

    1. 散列的概念 散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存 ...

  7. 【C/C++】散列/算法笔记4.2

    先说一下我自己的理解. 我先给你N组数据,这个N组里可能有重复的! 然后我们先统计好了N组里面的独立的每个对应的出现了几次(相当于map,然后每项属性有出现了多少次的),用的是数组下标对应 现在我们给 ...

  8. Java 对字符串数据进行MD5/SHA1哈希散列运算

    Java对字符串数据进行MD5/SHA1哈希散列运算 [java] view plain copy package cn.aibo.test; import java.security.Message ...

  9. Java 消息摘要 散列 MD5 SHA

    package xxx.common.util; import java.math.BigInteger; import java.security.MessageDigest; import jav ...

随机推荐

  1. 剑指offer-面试题40-最下的k个数-快速排序

    /* 题目: 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. */ /* 思路: 快速排序,找到第k+1大的数,其左边则为最 ...

  2. ps调整图片指定区域的颜色,两种方法

    参考链接:http://tech.wmzhe.com/article/12329.html

  3. 怎么在IDEA中给方法添加分割线?

    方法中间分割不清晰 怎么在IDEA中给方法添加分割线呢? 效果如图 方法上有一条分割线,比较明了 按照下列顺序点击修改设置即可 File→Settings→Editor→General→Appeara ...

  4. Android记事本在菜单栏添加搜索按钮方法

    效果图 这个app结构和我之前将记事本开发的博客基本一致,我这里直接讲一下怎样添加 使用的开发软件为android studio 首先在res目录下新建文件夹menu,添加目录布局文件main_men ...

  5. EasyUI笔记(一)Base基础

    总结: 1)每个UI都是通过属性.事件和方法运作的 2)用JS加载时只需传入一个参数(用大括号{}包围),包含若干个键值对,之间用逗号隔开: 3)事件也是写在JS加载时的方法中,也是键值对形式,值是匿 ...

  6. jquery的封装与对象学习

    1.封装学习 /// <reference path="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" /> ...

  7. Java_Day6(上)

    Java learning_Day6 本人学习视频用的是马士兵的,也在这里献上 <链接:https://pan.baidu.com/s/1qKNGJNh0GgvlJnitTJGqgA> 提 ...

  8. 关于全球唯一标识GUID的生成

    1.c#生成GUID的几种方式 (1)生成标准的标志符 (36位标准) var strguid = Guid.NewGuid().ToString(); 结果:B2A5AB40-EE29-4791-9 ...

  9. 给静态网站的链接添加nofollow属性

    给网站的链接添加nofollow属性对SEO非常有效,如果是动态网站,实现的方法比较多,但是对于静态网站来说只能借助CSS或者JS来实现,单纯的CSS实现需要覆盖所有的链接出现位置:JS与CSS结合则 ...

  10. MarkDown图文编辑系列教程(二)

    一.写在前面 引言 本文是我写的MarkDown系列教程的第二篇,前一篇的地址:MarkDown图文编辑系列教程(一) 读完本篇,你将获得 学会使用markdown语法进行:区块引用(一种常用的引用格 ...