一文彻底搞定Java哈希表和哈希冲突

 更新时间:2021年05月14日 11:07:31   作者:恪愚  
本文介绍了什么是哈希表?什么是哈希函数?什么是哈希冲突?三个问题的解决方案,文中有非常详细的代码示例,对正在学习java的小伙伴们很有帮助,需要的朋友可以参考下

一、什么是哈希表?

哈希表也叫散列表,它是基于数组的。这间接带来了一个优点:查找的时间复杂度为 O(1)、当然,它的插入时间复杂度也是 O(1)。还有一个缺点:数组创建后扩容成本较高。
哈希表中有一个“主流”思想:转换。一个重要的概念是将「键」或「关键字」转换成数组下标。这由“哈希函数”完成。

二、什么是哈希函数?

由上,其作用就是将非 int 的键/关键字转化为 int 的值,使可以用来做数组下标。
比如,HashMap 中就这样实现了哈希函数:

static final int hash(Object key){
	int h;
	return (key==null)?0:(h=key.hashCode())^(h>>>16);   // 通过异或提高hash的“散列度”,降低冲突
}

其中利用了 hashCode 完成转换。虽然哈希函数有很多种实现,但都应当满足这三点:

  • 计算得到的是非负整数;
  • 如果 key1==key2,则 hash(key1)==hash(key2)
  • 如果 key1!=key2,则 hash(key1)!=hash(key2)
并不是所有的键/关键字都需要被转换才能做下标(索引)就像 JS 中也有类似的、但仅用于检测键是否能用来做数组下标的方法:JavaScript数组索引检测中的数据类型问题

三、什么是哈希冲突?

上面提到了 hashMap —— 一个java中提供的数据集。我们先来了解下:首先,hashMap 本质上是一个容器,它为了达到快速索引的目的,使用了数组结构“快速定位”的特性。
hashMap 中为了更快找到插入的值,建立了插入值和数组下标的关系:pos(下标)=key(值)%size(数组大小)

比如:数组长度为10

1.插入100,有100%10=0;

2.插入201,有201%10=1;

3.插入403,有403%10=3;

array1

但是如果这样设计的话,我现在再插入200,会怎么样?
这就是数组的一个缺点:插入特殊值比较“费劲”。不如我们干脆将数组涉及成这样:

link1

引入链表特性,一个节点就包括一个值和一个next指针。

现在再插入上面那些值,就变成了这样:

link2

这时候如果再插入值300,怎么做?

link3

类似这样(当两个或以上的key的pos相同,且key不同)其实就是我们提到的“hash冲突”,而 hashMap 中解决hash冲突的方法就是上面说的“单链表”!
但是这又有一个问题:虽然用有序链表的方式可以减少不成功的查找时间(因为只要有一项比查找值大,就说明没有我们需要查找的值),但是不能加快成功的查找。如果冲突的链表太长,则链表查找时需要从“头”遍历的劣势就暴露出来了 —— 针对这个问题,JDK1.8后用 红黑树 做了优化!

但是我们先撇开红黑树,用单链表的形式说明一下哈希表的操作:

/**
 * 链表基类:链表法解决哈希冲突用的是有序链表!
*/
public class SortedLinkList {
    private Link first;
    public SortedLinkList(){
        first = null;
    }
    /**
     * 链表插入
     * @param link
     */
    public void insert(Link link){
        int key = link.getKey();
        Link previous = null;
        Link current = first;
        while (current!=null && key >current.getKey()){
            previous = current;
            current = current.next;
        }
        if (previous == null)
            first = link;
        else
            previous.next = link;
        link.next = current;
    }

    /**
     * 链表删除
     * @param key
     */
    public void delete(int key){
        Link previous = null;
        Link current = first;
        while (current !=null && key !=current.getKey()){
            previous = current;
            current = current.next;
        }
        if (previous == null)
            first = first.next;
        else
            previous.next = current.next;
    }

    /**
     * 链表查找
     * @param key
     * @return
     */
    public Link find(int key){
        Link current = first;
        while (current !=null && current.getKey() <=key){
            if (current.getKey() == key){
                return current;
            }
            current = current.next;
        }
        return null;
    }
}

链表法哈希表插入:

public void insert(int data) {
    Link link = new Link(data);
    int key = link.getKey();
    int hashVal = hash(key);
    array[hashVal].insert(link);
}

链表法哈希表查找:

public Link find(int key) {
    int hashVal = hash(key);
    return array[hashVal].find(key);
}

链表法哈希表删除:

public Link find(int key) {
    int hashVal = hash(key);
    return array[hashVal].find(key);
}

除了链表法,解决哈希冲突还有一个方法:开放寻址法。
在开放地址法中,若数据不能直接存放在哈希函数计算出来的数组下标时,就需要寻找其他位置来存放。在开放地址法中有三种方式来寻找其他的位置,分别是

  • 线性探测
  • 二次探测
  • 再哈希法

到此这篇关于一文彻底搞定Java哈希表和哈希冲突的文章就介绍到这了,更多相关Java哈希表和哈希冲突内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java接口方法默认静态实现代码实例

    Java接口方法默认静态实现代码实例

    这篇文章主要介绍了Java接口方法默认静态实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • 浅谈SpringBoot如何封装统一响应体

    浅谈SpringBoot如何封装统一响应体

    今天带各位小伙伴学习SpringBoot如何封装统一响应体,文中有非常详细的介绍及代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-05-05
  • Java实现warcraft java版游戏的示例代码

    Java实现warcraft java版游戏的示例代码

    致敬经典的warcraft,《warcraft java版》是一款即时战略题材单机游戏,采用魔兽原味风格和机制。本文将用java语言实现,采用了swing技术进行了界面化处理,感兴趣的可以了解一下
    2022-09-09
  • Java的Object类九个方法技巧

    Java的Object类九个方法技巧

    这篇文章主要介绍了Java的Object类九个方法技巧,Java的Object 类的完整路径是java.lang.Object ,是所有类的父类编译,下文相关资料,需要的朋友可以参考一下
    2022-04-04
  • Java实现经典游戏俄罗斯方块(升级版)的示例代码

    Java实现经典游戏俄罗斯方块(升级版)的示例代码

    俄罗斯方块是一款风靡全球,从一开始到现在都一直经久不衰的电脑、手机、掌上游戏机产品,是一款游戏规则简单,但又不缺乏乐趣的简单经典小游戏。本文将用Java语言实现这一经典游戏,需要的可以参考一下
    2022-09-09
  • Java中全局变量和局部变量详解(看这篇就够了)

    Java中全局变量和局部变量详解(看这篇就够了)

    在Java中全局变量和局部变量是两种不同作用域的变量,这篇文章主要给大家介绍了关于Java中全局变量和局部变量的相关资料,文中通过代码介绍的非常详细,大家看这篇就够了,需要的朋友可以参考下
    2023-11-11
  • springboot远程执行服务器指令

    springboot远程执行服务器指令

    这篇文章主要介绍了springboot远程执行服务器指令,本例是java远程连接到服务器,去抓取查询kubesphere中的etcd日志,并返回,需要的朋友可以参考下
    2023-09-09
  • maven如何利用springboot的配置文件进行多个环境的打包

    maven如何利用springboot的配置文件进行多个环境的打包

    这篇文章主要介绍了maven如何利用springboot的配置文件进行多个环境的打包,在Spring Boot中多环境配置文件名需要满足application-{profiles.active}.properties的格式,其中{profiles.active}对应你的环境标识,本文给大家详细讲解,需要的朋友可以参考下
    2023-02-02
  • Java中如何灵活获取excel中的数据

    Java中如何灵活获取excel中的数据

    这篇文章主要给大家介绍了关于Java中如何灵活获取excel中的数据,在日常工作中我们常常会进行文件读写操作,除去我们最常用的纯文本文件读写,更多时候我们需要对Excel中的数据进行读取操作,需要的朋友可以参考下
    2023-07-07
  • 使用Spring框架实现用户登录

    使用Spring框架实现用户登录

    这篇文章主要为大家详细介绍了使用Spring框架实现用户登录,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09

最新评论