前言

在入学时,学校为我们每位童鞋建立一个档案信息,当然每个档案信息都对应档案编号,还有比如在学校图书馆,图书馆为每本书都编了唯一的一个书籍号,那么问题来了,当我们需要通过档案号快速查到对应档案信息或者通过书记号快速查到对应书籍,这个时候我们可以通过哪种数据结构呢?前面几节我们详细讲解了ArrayList和LinkedList,我们知道ArrayList底层就是一维数组,但是我们事先不知道在数组中的索引,此时查询到对应档案编号或书籍号需要循环遍历,这个时候时间复杂度肯定不是O(1),即使我们知道索引但是若索引键很大此时不再适合作为数组的索引,若通过LinkedList双向链表查询,通过我们的分析肯定也不是O(1),这个时候就需要用到哈希算法则获取的时间复杂度为恒定时间O(1)。我们习惯称之为哈希,实际叫作散列,散列是一种用于从一组相似对象中唯一标识特定对象的技术。

散列

在散列中,通过使用散列函数将大键转换为小键,然后将这些值存储在称为哈希表的数据结构中。散列的基本思路是在数组中统一分配条目(键/值对),为每个元素分配一个键(转换键),通过键查找,我们可以在O(1)时间内访问到对应元素。使用散列函数计算到一个索引,该索引建议可以找到或插入元素的位置。散列分如下两步执行:通过使用散列函数将元素转换为整数。此元素可用作存储原始元素的索引,该元素属于哈希表。该元素存储在哈希表中,可以使用散列键快速检索它。

hash = hashfunc(key)
index = hash%array_size

上述最重要的是通过散列函数获取键的散列值,然后将得到的散列值对数组大小去模即存放到哈希表中的索引地址。到在此方法中,散列与数组大小无关,然后通过使用模运算符(%)将其缩减为索引(介于0和array_size之间的数字 - 1)。要实现良好的散列机制,具有以下基本要求的良好散列函数非常重要:

易于计算:它应该易于计算,并且不能成为算法本身(不是为了算法而算法)。

统一分布:它应该在哈希表中提供统一分布,不应导致聚集。

较少的冲突:应尽量避免不同元素映射到相同的哈希值时发生的冲突。

注意:无论散列函数有多好,发生冲突是必然的,因此,为了保持哈希表的性能,通过各种冲突解决技术来管理冲突是很重要的。使用散列函数存储对象的步骤:创建一个大小为M的数组。选择一个哈希函数h,即从对象到整数0,1,...,M-1的映射。 将这些对象放入通过散列函数index = h(object)计算的索引的数组中,这种数组称为哈希表。那么我们如何选择哈希函数? 创建哈希函数的一种方法是使用Java的hashCode()方法。 hashCode()方法在Object类中实现,因此Java中的每个类都继承它。 哈希码提供了对象的数字表示,我们来看看如下代码示例:

        String obj1 = String.valueOf(4);
String obj2 = String.valueOf(16);
String obj3 = String.valueOf(68);
String obj4 = String.valueOf(125);
String obj5 = String.valueOf(255); System.out.println(obj1.hashCode() % 5);
System.out.println(obj2.hashCode() % 5);
System.out.println(obj3.hashCode() % 5);
System.out.println(obj4.hashCode() % 5);
System.out.println(obj5.hashCode() % 5);

如上哈希数组大小为5,我们创建的哈希函数是使用的Java中提供给我们的hashcode方法,上图中打印出的数字即为在哈希表中的索引存放地址。此时我们发现obj4和obj1在哈希表中的存放地址一样,这个也就是我们所说的冲突。在散列中解决冲突有四种方式:(1)开放寻址法或者叫线性探测或者叫闭合散列、(2)再哈希法、(3)链地址法、(4) 建立公共溢出区。在这里呢我给大家演示常见的两种,线性探测或称为开放寻址法和链地址法,首先我们来看看开放寻址法。

散列冲突之开放寻址法

在开放式寻址中,所有条目记录都存储在数组本身中,而非链接列表中。当我们插入新的元素或条目时,首先计算散列值的哈希索引,然后检查数组(从散列索引开始)。如果散列索引地址未被占用,则将条目记录插入散列索引处地址中,否则它将以某个探测序列继续进行,直到找到未占用的地址。探测序列是遍历条目时遵循的序列。在不同的探测序列中,连续的入口槽或探针之间可以有不同的间隔。搜索条目时,将以相同的顺序扫描阵列,直到找到目标元素或找到未使用的地址,这也就表明哈希表中没有这样的键,名为“开放寻址”指的是元素地址不是由其散列值所确定。线性探测是指连续探测之间的间隔固定(通常为1)。假设特定条目的散列索引是索引。线性探测的探测序列将是:

index = index % hashTableSize
index = (index + 1) % hashTableSize
index = (index + 2) % hashTableSize
index = (index + 3) % hashTableSize

.......

如上意思表明当指定键的哈希值已被占用,则将哈希值以间隔为1进行递增,如此一次递增直到找到未被占用的索引存放地址,代码如下:

public class HashTable {

    //数组容量
private int capacity; //哈希键值对数组
private Entry[] entries = {}; public HashTable(int capacity) {
this.capacity = capacity;
entries = new Entry[this.capacity];
} //添加键值对
public void put(String key, String value) {
final Entry hashEntry = new Entry(key, value);
int hash = getHash(key);
entries[hash] = hashEntry;
} //获取键哈希值
private int getHash(String key) {
int hashCode = key.hashCode();
int hash = hashCode % capacity;
while (entries[hash] != null) {
hashCode += 1;
hash = hashCode % capacity;
}
return hash;
} //获取指定键值
public String get(String key) {
int hashCode = key.hashCode();
int hash = hashCode % capacity;
if (entries[hash] != null) {
while (!entries[hash].key.equals(key))
{
hashCode += 1;
hash = hashCode % capacity;
}
return entries[hash].value;
}
return null;
} private class Entry {
String key;
String value; public Entry(String key, String value) {
this.key = key;
this.value = value;
}
}
}

我们在控制台将如上测试数据添加到我们自定义的哈希表类中,然后去查询对应键的值,如下:

public class Main {

    public static void main(String[] args) {

        HashTable table = new HashTable(5);

        table.put(String.valueOf(4), String.valueOf(4));
table.put(String.valueOf(16), String.valueOf(16));
table.put(String.valueOf(68), String.valueOf(68));
table.put(String.valueOf(125), String.valueOf(125));
table.put(String.valueOf(255), String.valueOf(255)); System.out.println(table.get(String.valueOf(4)));
System.out.println(table.get(String.valueOf(125)));
}
}

散列冲突之链地址法

我们通过使用单链表来实现链地址法,链地址法是最常用的冲突解决技术之一,在单链表中,哈希表的每个元素都是链表,要想在哈希表中存储元素,必须将其插入特定的链表。如果存在任何冲突(即两个不同的元素具有相同的散列值),则将这两个元素存储在同一链表中,查找的成本是扫描所选链表的条目以获得所需的键,如果键的分布足够均匀,则查找的平均成本仅取决于每个链表的平均键数。对于链地址法,最坏的情况是所有条目都插入到同一个链表中。查找过程可能必须扫描其所有条目,因此最坏情况放入成本与表中条目的数量(N)成比例,链地址法存在哈希表中如下示意图:

在开头我们所给的例子,键16和125的哈希值相同则我们会将其存放到同一链表中,接下来我们通过示例代码来实现链地址法,如下:

public class HashTable {

    //数组容量
private int capacity; //哈希键值对数组
private Entry[] entries = {}; public HashTable(int capacity) {
this.capacity = capacity;
entries = new Entry[this.capacity];
} //添加键值对
public void put(String key, String value) {
//获取键哈希值
int hash = getHash(key); //实例化类存放键和值
final Entry hashEntry = new Entry(key, value); //如果在数组中未有冲突的键则直接存放
if(entries[hash] == null) {
entries[hash] = hashEntry;
} //如果找到冲突的哈希值则存放到单链表中的下一引用
else {
Entry temp = entries[hash];
while(temp.next != null) {
temp = temp.next;
}
temp.next = hashEntry;
}
} //获取键哈希值
private int getHash(String key) {
return key.hashCode() % capacity;
} //获取指定键值
public String get(String key) { int hash = getHash(key); if(entries[hash] != null) { Entry temp = entries[hash]; while( !temp.key.equals(key)
&& temp.next != null ) {
temp = temp.next;
}
return temp.value;
} return null;
} private class Entry {
String key;
String value;
Entry next; public Entry(String key, String value) {
this.key = key;
this.value = value;
this.next = null;
}
}
}

控制台代码和演示开放寻址法一样且打印出的结果也一致,这里就不再给出。到这里我们实现了散列算法以及散列算法中解决冲突最常用的技术:开放寻址法和链地址法。

总结

本节我们还是一如既往先了解对应概念的算法实现为我们下一节详细分析Hashtable做铺垫,好了,本节内容我们到此为止,感谢您的阅读,我们下节见。

哈希算法原理【Java实现】(十)的更多相关文章

  1. 一致性哈希算法原理及Java实现

     一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单 ...

  2. 一致性哈希算法原理、避免数据热点方法及Java实现

     一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单 ...

  3. 感知哈希算法的java实现

    一.原理讲解      实现这种功能的关键技术叫做"感知哈希算法"(Perceptual Hash Algorithm), 意思是为图片生成一个指纹(字符串格式), 两张图片的指纹 ...

  4. Java_一致性哈希算法与Java实现

    摘自:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  5. 一致性哈希算法与Java实现

    原文:http://blog.csdn.net/wuhuan_wp/article/details/7010071 一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具 ...

  6. 负载均衡-基础-一致性哈希算法及java实现

    一致性hash算法,参考: http://www.blogjava.net/hello-yun/archive/2012/10/10/389289.html 针对这篇文章,加入了自己的理解,在原有的代 ...

  7. 一致性哈希算法(consistent hashing)(转)

    原文链接:每天进步一点点——五分钟理解一致性哈希算法(consistent hashing)  一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网 ...

  8. 对一致性Hash算法,Java代码实现的深入研究(转)

    转载:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读 ...

  9. 白话解析:一致性哈希算法 consistent hashing【转】

    学习一致性哈希算法原理的时候看到博主朱双印的一片文章,看完就懂,大佬! 白话解析:一致性哈希算法 consistent hashing

随机推荐

  1. Android Studio 中java 文件报错红色J

    用常用的方法清除Android Studio的缓存然后重启,"File" -> "Invalidate Cashes / Restart" -> & ...

  2. 跟着文档学习gulp1.1安装入门

    Step1:检查是否已经安装了node,npm 和 npX是否正确安装 Step2:安装gulp命令行工具(全局安装gulp) npm install --global gulp-cli Step3: ...

  3. Redis Cluster 的数据分片机制

    上一篇<分布式数据缓存中的一致性哈希算法> 文章中讲述了一致性哈希算法的基本原理和实现,今天就以 Redis Cluster 为例,详细讲解一下分布式数据缓存中的数据分片,上线下线时数据迁 ...

  4. npm 镜像配置

    npm 默认 registry:https://registry.npmjs.org/  npm中文文档:https://www.npmjs.cn   淘宝 NPM 镜像:https://npm.ta ...

  5. Linux中环境变量相关文件的区别

    Linux下各种不同环境变量相关文件的作用: 1. /etc/environment  设置整个系统的环境,系统启动时,该文件被执行. 2. /etc/profile     设置所有用户的环境,当用 ...

  6. Linux Redis 安装(带视频)

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 疯狂创客圈 高并 ...

  7. CentOS 线上搭建 jupyter_server 笔记

    一.背景 为公司负责 Data Science 的同事配置线上 jupyter_server (jupyter + jupyter_kernel_gateway)环境. 二.环境 CentOS 7.6 ...

  8. PlayJava Day022

    List接口: ArrayList:数组集合,底层使用数组,查询快,增删慢 LinkedList:链表集合,底层使用链表形式,查询慢,增删快 注意: 对于随机访问get和set,ArrayList优于 ...

  9. pyhton的安装,环境变量的设置,pycharm的安装下载,中文汉化和字体的设置

    1.下载pycharm https://www.7down.com/soft/336988.html 1.pycharm的汉化下载汉化包:resources_cn.jar    放到pycharm的安 ...

  10. Python如何运行程序

    人生苦短,我用Python. 作为一个开发人员,如何写代码是必须要知道的,代码如何运行也是有必要了解的.通过了解代码如何运行,可指导我们写出效率更高的代码.下午看了<Python学习手册> ...