散列表

散列表(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. 获取redis实例绑定cpu的情况

    redis是一个单线模型的nosql类型的数据库,而目前接触到的服务器大都是多核的,比如8c,16c,32c,64c等等.为了充分利用主机,在一台主机上必然会部署多个redis实例,默认情况cpu会随 ...

  2. JAVA全套资料含视频源码(持续更新~)

    本文旨在免费分享我所搜集到的Java学习资源,所有资源都是通过正规渠道获取,不存在侵权.现在整理分享给有所需要的人. 希望对你们有所帮助!有新增资源我会更新的~大家有好的资源也希望分享,大家互帮互助共 ...

  3. B. Modulo Equality

    当时想到的第一个想法是用拓展欧几里得解方程,求x的最小正解.一发交了之后发现爆long long,因为m是1e9. 因此本题的正解是暴力,保证有解的情况下,那么a数组中的一个数必然对应着b数组中的一个 ...

  4. SSH远程登录、.sh文件后缀运行、l l命令结果说明、VIM模式切换

    目录 SSH远程登录..sh文件后缀运行.l l命令结果说明.VIM模式切换 SSH远程安全登录 .sh文件后缀运行 l l命令结果说明 VIM模式切换 SSH远程登录..sh文件后缀运行.l l命令 ...

  5. 自适应阈值化操作:adaptiveThreshold()函数

    在图像阈值化操作中,更关注的是从二值化图像中,分离目标区域和背景区域,但是仅仅通过设定固定阈值很难达到理想的分割效果.而自适应阈值,则是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值.这样 ...

  6. 第31届IMO 第2题

    题目 设n>=3,考虑一个圆上由2n-1个不同点构成的集合E.现给E中恰好k个点染上黑色,如果至少有一对黑点使得这两个黑点之间的弧上(两段弧中的某一个)包含恰好E中的n个点,就成这样的染色方法是 ...

  7. Java数三退一问题

    问题描述 有100人围成一圈,顺序排号.从第1个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来的第几号的那位. 代码实现: public class Count3Quit1 { ...

  8. 请求 - Fetch(未完)

    概念和用法 可以被使用到更多地应用场景中:无论是service workers.Cache API.又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式. Cache 接口 ...

  9. 如何实现 List 集合的线程安全

    在实际项目开发中,List 集合较为常用,相比于数组,其提供了更多的方法,便于遍历.搜索.添加与移除.常见的有 ArrayList.Vector等.   关于ArrayList 中所周知,ArrayL ...

  10. Linux 宝塔面板忘记密码的解决方案

    进入ssh 输入以下命令重置密码(把命令最后面的   “testpasswd”  替换成你要改的新密码)注:若是debian/ubuntu用户,请使用有root权限的账户去执行这条命令 cd /www ...