Java 散列集笔记
散列表
散列表(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
散列冲突的解决方法
开放地址法 当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。 公式: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 = 伪随机序列 伪随机再散列; 注意:对于利用开放地址法处理冲突所产生的哈希表中删除一个元素时需要谨慎,不能直接地删除,因为这样将会截断其他具有相同哈希地址的元素的查找地址,所以,通常采用设定一个特殊的标志以示该元素已被删除。
链地址法 如果散列表空间为 0 ~ m – 1 ,设置一个由 m 个指针分量组成的一维数组 ST[ m ], 凡散列地址为 i 的数据元素都插入到头指针为 ST[ i ] 的链表中。这种方法有点近似于邻接表的基本思想,且这种方法适合于冲突比较严重的情况。
再哈希法 当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突。 缺点:计算时间增加。
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》
Java 散列集笔记的更多相关文章
- Java学习笔记(2)----散列集/线性表/队列/集合/图(Set,List,Queue,Collection,Map)
1. Java集合框架中的所有实例类都实现了Cloneable和Seriablizable接口.所以,它们的实例都是可复制和可序列化的. 2. 规则集存储的是不重复的元素.若要在集合中存储重复的元素, ...
- java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列
java 散列与散列码探讨 ,简单HashMap实现散列映射表运行各种操作示列 package org.rui.collection2.maps; /** * 散列与散列码 * 将土拔鼠对象与预报对象 ...
- java 散列
原文:https://www.cnblogs.com/younghao/p/8333795.html 为什么要设计散列这种数据结构呢?在现实世界中,实体之间可能存在着映射关系(key-value),比 ...
- java 散列运算浅分析 hash()
文章部分代码图片和总结来自参考资料 哈希和常用的方法 散列,从中文字面意思就很好理解了,分散排列,我们知道数组地址空间连续,查找快,增删慢,而链表,查找慢,增删快,两者结合起来形成散列 ...
- Java散列和散列码的实现
转自:https://blog.csdn.net/al_assad/article/details/52989525 散列和散列码 ※正确的equals方法应该满足的的条件: ①自反性:x.equ ...
- 数据结构与算法分析java——散列
1. 散列的概念 散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h(K)(称为散列函数),计算出对应的函数值来,把这个值解释为结点的存储地址,将结点存 ...
- 【C/C++】散列/算法笔记4.2
先说一下我自己的理解. 我先给你N组数据,这个N组里可能有重复的! 然后我们先统计好了N组里面的独立的每个对应的出现了几次(相当于map,然后每项属性有出现了多少次的),用的是数组下标对应 现在我们给 ...
- Java 对字符串数据进行MD5/SHA1哈希散列运算
Java对字符串数据进行MD5/SHA1哈希散列运算 [java] view plain copy package cn.aibo.test; import java.security.Message ...
- Java 消息摘要 散列 MD5 SHA
package xxx.common.util; import java.math.BigInteger; import java.security.MessageDigest; import jav ...
随机推荐
- oracle分组后取最新的记录
使用Group By来实现取最新记录,需要注意一个问题,如果最大时间相同的数据都会被取出来. PS:即使数据字段类型是timestamp,也会登录相同的时间的数据. select A.* from A ...
- shiro权限认证Realm的四大用法
一.SimpleAccountRealm public class AuthenticationTest { SimpleAccountRealm sar=new SimpleAcc ...
- Newtonsoft--自定义格式化日期
public string ToJson(object obj, string timeFormat) { try { IsoDateTimeConverter timeConverter = new ...
- 【剑指Offer】59:对称的二叉树
题目描述 请实现一个函数,用来判断一颗二叉树是不是对称的.注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的. 题解:递归 public static boolean isSymmetric ...
- 使用 Express 脚手架
安装: npm install -g express-generator 创建项目: express myapp 安装依赖 install dependencies: > npm install ...
- 用JavaScript设计和创建对象
通过在优锐课的java学习分享中,get很多学习新技能,分享给大家参考学习. 介绍 在阅读此分步指南之前,你可能需要关注面向对象编程的介绍. 以下步骤中包含的Java代码与该文章理论中使用的Book对 ...
- 关于GET和POST请求的区别,最通俗全面的回答
GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一二. 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数. 你可能自己 ...
- 重启监听卡在connecting to的问题
问题描述:lsnrctl start启动监听起不来,一直卡在connecting to半天 1.[oracle@orcl ~]$ lsnrctl start 一直卡半天,就是连不上,按照以前的解决办法 ...
- ASP.NET Identity系列教程-4【Identity高级技术】
https://www.cnblogs.com/r01cn/p/5194257.html 15 ASP.NET Identity高级技术 In this chapter, I finish my de ...
- map/reduce+lambda让程序简单化
map()函数 map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回. 也就是 map(f,[x,x,x, ...