HashMap(2)-----哈希表

news2024/12/26 12:44:13

自己实现一个哈希表

class Node
{   int data;
    String val;
    Node next;
    public Node(int data,String val)
    {    this.val=val;
        this.data=data;
    }
}
class myhashtable
{    Node arr1[];
    Node head=null;
    Node tail=null;
    int count=0;
    private double load=0.75;
    public myhashtable() {
        this.arr1 = new Node[10];
    }
    public void reasize()
    {
            Node[] arr2=new Node[2*(arr1.length)];
//新创建一个数组,长度为原来长度的二倍,这里面不可以用Arrays.Copy来进行拷贝
        for(int i=0;i<arr1.length;i++)
        {
            Node current=arr1[i];
            while(current!=null)
            {  Node curentnxst=current.next;
                int key=current.data%(arr2.length);//获取新的下标
                current.next=arr1[key];
                arr2[key].next=current;
                current=curentnxst;
            }
        }
        this.arr1=arr2;

    }
    public int push(int key,String str)
    {    if((1.0)*(count/arr1.length)>load)
    {
       reasize();
    }
        Node node=new Node(key,str);
        int index=key%arr1.length;
        Node current=arr1[index];
        while(current!=null)//先遍历数组下标的整个链表,发现如果key相同,那么就更新val的值
        {     if(current.data==key)
        {
            current.val=str;
            return key;
        }
            current=current.next;
        }
        node.next=arr1[index];
        arr1[index]=node;
        count++;

        return -1;

    }


}

1)链表的长度不会很长,控制在常数范围内,从JDK1.8开始,当数组长度超过64况且链表长度超过8就会转化成红黑树,也就是说虽然哈希表一直在和冲突作斗争,但是我们认为哈希表的冲突概率是不高的,冲突个数是可控的,也就是说每一个桶中的链表长度是一个常数,所以我们通常情况下认为哈希表的插入删除,查找的时间复杂度是O(1)

2)对于扩容来说:用扩容之后,原来桶中的所有数据(数组中每一个链表的每一个元素必须要进行重新哈希),要遍历原来的链表,这个一定要注意不能在原来的数组上面进行2倍扩容,假设原来数组长度是10,没扩容之前数据14是被放在4下标的,但是假设扩容之后变成2倍,数据14就被放在14下标了

3)上面我们的哈希表是整形,但如果是任意类型,怎么办,如果key是String类型,总不可以让

一个字符串来对一个数组长度求余数吧;

上代码:

class Hello{
    class Node<K,V>
    {
        K k;
        V v;
        Node next;

        public Node(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }
public class hasntable<K,V>
{
    Node arr1[];
    int count=0;

    public hasntable() {
        Node arr2[] = (Node[])new Node[10];
    }
    public void reasize()
    {     Node[]arr2=new Node[2*(arr1.length)];
        for(int j=0;j<arr1.length;j++)
        {
               Node current=arr1[j];
               while(current!=null)
               {
                   Node currentnext=current.next;
                   int hash=current.k.hashCode();
                   int index=hash%arr2.length;
                   current.next=arr1[index];
                   arr1[index].next=current;
                   current=currentnext;
               }
        }
    }
    public void push(K k,V v)
    {   if((1.0)*(count)/(arr1.length)>0.75)
       {
        reasize();
       }
        // int index=k%arr1.length;此时k为引用类型
        int hash=k.hashCode();
        int index=hash%arr1.length;
        Node<K,V> current=arr1[index];
        while(current!=null)
        {
            //if(current.k==k)//是引用类型不可以直接比较虽然不报错,但是比较比较的是地址,而不是自定义类型的内容
            if(current.k.equals(k))
            {
                current.v=v;
                return;
            }
            current=current.next;
        }
        Node<K,V> node=new Node<>(k,v);
        node.next=arr1[index];
        arr1[index]=node;
        count++;
    }
}

1)我们在向Map中写入自定义类型的时候,一定要重写hashCode和equals方法,当HashMap<Person, String> map=new HashMap<>();我们希望把id相同的Person放到哈希表的相同位置;

2)我们是用hashcode来确定这个k的位置上,使用equals比较哪一个k和我们当前这个k是相同的

package Demo;

import java.util.Objects;

class Person{
    public int age;
    public String name;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }
}
public class HelloWorld{
    public static void main(String[] args) {
        Person person1=new Person("李佳伟",10);
        Person person2=new Person("李佳伟",10);
//我们认为person1和person2是同一个人,应该放在数组的同一个位置
按理说他们得到的hashcode%array.length应该是相同的,但是运行的时候
//发现他们的哈希值不一样,但是我们重写hashcode之后,得到的hashcode是相同的
这样我们就可以认为两个逻辑一样的人,一定会存放到同一个位置
//当自定义类型作为Key值的时候,一定要重写我们的hashcode,否则就会出现本以上两个一样的人
最终你的代码在逻辑上认为他不是一个人了


        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }


}

1)hashcode一样,那么equals不一定一样,hashcode一样只能证明两个元素在数组的相同位置,但是一个数组的一个位置下面有多个元素组成的链表

2)equals相同,hashcode一定相同

package Demo;
import java.util.Objects;
class Person{
    public int age;
    public String name;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(age, name);
    }
}
class MyHashMap<K,V>{
      public int UsedSize=0;
      public Node<K,V>[] array=(Node<K, V>[])new Node[10];
    static class Node<K,V>{
       public K k;
       public V v;
       public Node next;
       public Node(K k,V v)
       {
           this.k=k;
           this.v=v;
       }
    }
    public void put(K k,V v)
    {
        int index=k.hashCode()%array.length;//得到这个值对应的数组下标,必须重写hashcode
        //否则两个逻辑上相同的值会得到不同的哈希值,就会被放到数组种不同的位置
        Node current=array[index];
        while(current!=null){
            if(current.k.equals(k))//不能使用==
                //现在k是一个引用类型,使用==默认比较的是地址,所以我们使用equals,但是直接使用equals比较的还是两个引用的地址《
               //所以我们要重写equals方法,来进行比较他们具体的内容
            {
                 current.v=v;
                 return;
            }
        }
        //我们是头插法来进行插入元素
        Node<K,V> node=new Node<>(k,v);
        node.next=array[index];
        array[index]=node;
        UsedSize++;
        //判断是否负载因子超过了0.75,如果超过,就进行扩容
        if(UsedSize/array.length>0.75){
            CreateBigSizeArray(array);
        }
    }

    private void CreateBigSizeArray(Node<K,V>[] array) {
        Node[] newArray=new Node[2* array.length];
        for(int i=0;i<array.length;i++){//遍历每一个哈希桶,也就是说遍历数组的每一个元素
            Node current=array[i];//遍历数组下的每一个链表
            while(current!=null){
                Node child=current.next;
                int index=current.v.hashCode()%newArray.length;
                current.next=array[index];
                array[index]=current;
                current=child;
            }
        }
    }
}
public class HelloWorld{
    public static void main(String[] args) {
        MyHashMap<Person,String> map=new MyHashMap<>();
        map.put(new Person("李佳伟",90),"bit");
        map.put(new Person("李嘉欣",100),"kig");
        System.out.println(map);

    }

}

解析HashMap源码:

1)public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable(是可序列化的)

AbstractMap是一个普通的抽象类,Serializable是可序列化的,说明可以把一个对象变成字符串

2)static final int DEFAULT_INITIAL_CAPACITY=1<<4;说明他的默认容量是16,必须是2的次幂

3)static final int MAXIMUM_CAPACITY=1<<30;说明他的最大长度是2^30;

4)static final float DEFAUIT_LOAD_FACTOR=0.75f;默认的负载因子

5)static final TREEINF_THRESHOLD=8;这是树化的条件(链表长度超过8)

6)static final int UNTREEIFY_THRESHOLD=6;不树化

这是桶的链表还原阈值:即红黑树转化成链表的值,当进行扩容之后,此时的HashMap的值会进行重新计算,在进行重新计算存储位置后当原有的红黑树数量小于6之后,会将红黑树转化成链表

7)static final int min_treeinfy_capacity=64

8)Node在哈希表中是一个内部类

9)HashMap一共有三个构造方法

1))无参构造方法

2))传输一个初始容量

3))传输一个初始容量和指定负载因子

位运算算效率高

public V put(K key,V value)

{    

        return putVal(hash(key),key,value,false,true);//第四个参数表示是老元素的值不会保留,会进行覆盖

static final int hash(Object Key)

{

     int h;

     return (key==null?0:(h==key.hashcode())^(h>>>16);

//得到的哈希地址已经是32位了,为了混合哈希值的高位和低位,高半区和低半区做异或,混合原始哈希码的高位和地位,增加低位的随机性,掺杂了高位的部分特征,混合之后的低位核心目的是为了让hash值的散列度更高,尽可能减少hash表的hash冲突,从而提升数据查找的性能,并且混合后的值也保持了高位的特征

}

这个哈希函数的作用就是根据key的哈希值来进行计算这个元素在数组中的位置,求解这个哈希值的过程就是哈希算法,异或就是相同为0,不同为1

1)当调用没有参数的构造方法的时候

当数组长度是0或者数组的引用为空的时候,第一次put操作的时候,就会执行reasize()的方法来进行扩容,默认的初始容量是16;

2)根据哈希值来进行计算索引的时候

(在寻找数组的下标的时候,在咱们之前的代码中时使用Key的哈希值%数组长度,但是在HashMap的源码中是用数组下标=(数组长度-1)&hash)

4&15==4%16如果数组的长度是2的次幂,这样hash%n-1的值(也就是得到位置)相等,位运算的速度更快,效率更高;再会new Node;

(n-1)&hash保证n是偶数(&都为1才是1,否则就是0)

如果n是偶数,那么n-1的最后一位一定是1,当与hash函数(最后一位有可能是进行0也有可能是1)&运算的时候,得到的最后一位是0或者1;即有可能是奇数也有可能是偶数

如果n是奇数,那么n-1的最后一位是0,那么与hash函数进行&操作的时候,会得到的下标的最后一位为0;最后只能得到偶数下标;

就是说我们以初始容量为16来进行举例,16-1=15,那么15的二进制序列就是001111,我们可以看出一个奇数二进制最后一位必然是1,当一个hash值参与运算的时候,最后一位可能是1,也有可能是0,当一个偶数和hash值进行与运算的时候最后一位必然是0,会造成有些位置永远也无法映射上值

3)保证数组容量是偶数,才可以保证最后的下标即是奇数下标又是偶数下标

 HashMap和HashTable的区别?

咱们的hashMap是允许key和value是空值的,但是hashtable这样的线程安全的集合数不允许插入空的key和value的,在咱们ConcurrentHashMap的源码当中,如果key为空,或者value为空,直接抛出空指针异常

1)两者最主要的区别在于Hashtable是线程安全,而HashMap则非线程安全

2)HashMap可以使用null作为key,不过建议还是尽量避免这样使用。HashMap以null作为key时,总是存储在table数组的第一个节点上。而Hashtable则不允许null作为key。

3)HashMap继承了AbstractMap,HashTable继承Dictionary抽象类,两者均实现Map接口。
4)HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
5)HashMap扩容时是当前容量翻倍即:capacity*2,Hashtable扩容时是容量翻倍+1即:capacity*2+1。
6)HashMap数据结构:数组+链表+红黑树(jdk1.8里加入了红黑树的实现,当链表的长度大于8时,转换为红黑树的结构),Hashtable数据结构:数组+链表。

7)HashMap和HashTable都实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。

8)计算哈希值也是不同的:hashMap先计算出哈希值,然后无符号右移16位,然后再进行按位与操作,但是hashTable

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

总结: 

1)首先查看数组长度是否为空,如果为空,就进行第一次扩容

2)计算索引:通过哈希算法,找到键值对在数组中的位置

3)插入元素:

3.1)如果当前元素位置为空,直接插入数据

3.2)不为空,判断是否为红黑树,是红黑树直接插入键值对

3.3)如果他不是红黑树,接下来判断,若链表长度大于8,数组长度超过64,直接将链表转化成红黑树,然后将数据插入到树中

3.4)如果不满足这两个条件的任意一个,那么直接遍历链表,key已经存在直接覆盖value

4)再次扩容,超过负载因子,直接进行扩容,重新哈希

5)超过数组容量进行扩容

HashMap的常见问题:

HashMap的节点有hash值,key,val,next

1)程序问题:

在HashMapJDK1.7的程序里面会出现死循环或者是数据覆盖的问题;死循环由于是HashMap的自身的运行机制再加上并发操作

1)比如说现在数组中的某一个元素下面挂着链表,链表中的元素从上到下的顺序是A B C        ,但是JDK1.7用的是头插法,那么进行扩容之后为数据位置是C B A,这是死循环的前提,是由于HashMap并发进行扩容而导致的

 

2)但是线程1扩容完成之后数据的变化为

 

2)数据覆盖问题:

1)线程T1进行添加的时候,判断某一个位置可以进行插入元素了,但还没有真正地进行插入操作,时间片就用完了(此时线程1已经判断好这个位置是空了,刚刚要进行插入操作,就被调度器给抢走了)

2)线程2也想要进行插入操作,并且T2要进行插入的数据产生的哈希值和T1要进行插入的数据是相同的,由于此位置没有任何元素(T1只是进行判断,刚想要插入值就被调度器给抢走了),于是此时线程2就把自己的值存入到当前位置了

3)T1线程恢复执行之后,因为非空判断已经执行完了,他是无法感知当前位置已经有值了,于是就把自己的值插入到了该位置,于是T2线程插入的值就被覆盖了

3)HashMap无序性问题------换成LinkedHashMap

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/46340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESIM:Enhanced LSTM for Natural Language Inference

原文链接&#xff1a;https://aclanthology.org/P17-1152.pdf 概述 对于自然语言推理任务&#xff0c;Bowman等人在2015年提出了一个大数据集&#xff0c;大多数工作就开始使用神经网络来对该任务进行训练。但作者认为序列模型的潜力还没有完全被挖掘&#xff0c;因此提出了一个…

自建网上商城平台该如何做好运营?

现在很多企业都在自建网上商城系统&#xff0c;但很多都以为建好商城上线就万事大吉了。其实&#xff0c;自建网上商城系统只是一个开始&#xff0c;后期的运营才最重要。如果经营不好&#xff0c;这个商城就白做了&#xff0c;今天小编给大家整理了几个网上商城平台运营方向&a…

[附源码]计算机毕业设计springboot港口集团仓库管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【python】list 删除列表中某个元素的3种方法;附加删除numpy数组中的指定索引元素的方法

方法 python中关于删除list中的某个元素&#xff0c;一般有三种方法: remove、pop、del实例 1.remove: 删除单个元素&#xff0c;删除首个符合条件的元素&#xff0c;按值删除 2.pop: 删除单个或多个元素&#xff0c;按位删除(根据索引删除) 3.del&#xff1a;它是根据索…

校内评奖评优|基于Springboot+Vue实现高校评优管理系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

[附源码]计算机毕业设计SpringBoot海南与东北的美食文化差异及做法的研究展示平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

新型超导Fluxonium量子比特正加速量子计算机的创建

量子处理器的一部分&#xff08;图片来源&#xff1a;网络&#xff09; 来自国立科技大学MISIS和鲍曼莫斯科国立技术大学的俄罗斯科学家是世界上最早使用超导Fluxonium量子比特实现双量子比特操作的科学家之一。Fluxonium具有更长的生命周期和更高的操作精度&#xff0c;因此它…

预约陪诊系统开发,跨省就医也能省时省力

就医陪护服务这几年一直受到人们的好评&#xff0c;有了预约陪诊系统开发之后一些无法居家照顾老人的子女可以通过就医陪护为老人预约服务&#xff0c;预约陪诊平台的出现还让陪诊员有了正规的接单平台&#xff0c;不仅方便了人们下单找就医陪诊员还可以对陪诊人员实行正规的管…

树状数组模板2【区间修改,单点询问】(线段树)

题目描述: 已知一个数列&#xff0c;你需要进行下面两种操作&#xff1a; 1.将某区间每一个数数加上x 2.求出该数列某个数的值 输入格式: 第一行包含两个整数N、M&#xff0c;分别表示该数列数字的个数和操作的总个数。 第二行包含N个用空格分隔的整数&#xff0c;其中第…

功率放大器可靠性怎么设计的好

功率放大器可靠性怎么设计怎么做&#xff0c;是很多工程师们心里一直存在的疑惑&#xff0c;像功率放大器这些有源放大电路&#xff0c;工作的可靠性在某些情况下比性能指标更重要。下面就来为大家介绍功率放大器可靠性怎么设计的好。 功率放大器电路设计过程中其可靠性主要分为…

hive报错 Too many bytes before newline: 2147483648

报错 Caused by: java.lang.RuntimeException: java.io.IOException: java.io.IOException: Too many bytes before newline: 2147483648at org.apache.hadoop.mapred.split.TezGroupedSplitsInputFormat$TezGroupedSplitsRecordReader.initNextRecordReader(TezGroupedSplits…

flex布局

flex是flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性&#xff0c;任何一个容器都可以指定为flex布局。 当我们为父盒子设为flex布局以后&#xff0c;子元素的float、clear和vertical-align属性将失效。 伸缩布局弹性布局伸缩盒布…

【微信小程序】一文带你读懂云开发

前言 云开发(CloudBase)是一个已经存在了很多年的概念&#xff0c;但在过去未能真正成为主流。然而&#xff0c;由于云和软件即服务的宏观趋势的结合&#xff0c;以及技术的进步&#xff0c;如容器技术 Docker 和 Kubernetes&#xff0c;云开发现在有机会成为基于云的应用程序的…

【Java】 java | sonar | sonar生成扫描token | 扫描示例 | 常见问题处理

一、说明 1、sonar已经安装OK 2、springboot项目 3、maven项目 二、生成token 1、登录到sonar 2、生成token 说明1&#xff1a; token仅生成一次&#xff0c;需保留 说明2&#xff1a; token忘记后&#xff0c;需要回收重新生成 3、执行命令 mvn sonar:sonar -Dsonar.projectK…

重新认识box-sizing

重新认识box-sizing 之前只是知道box-sizing设置成border-box,那么布局的时候&#xff0c;元素的宽高就是我们设置的宽高&#xff0c;省心方便。 今天仔细看了下 box-sizing的定义,有了新的认识 The box-sizing CSS property sets how the total width and height of an elem…

后端学习 -gRPC

gRPC 何为 RPC RPC 和 HTTP 的关系 基于 HTTP 的远程调用方案&#xff08;包含了接口规范、序列化反序列化等&#xff09;和 RPC 是并列的单从 HTTP 协议来看&#xff0c;RPC 可以使用 HTTP 作为通信协议 基于 HTTP 的远程调用方案和 RPC 的相同点、不同点 相同点 都可以实现…

RabbitMQ 快速入门 7 种简单模式

RabbitMQ 快速入门 7 种简单模式起步七种模式项目依赖1、"Hello World!"(1) Connection 方式(2) RabbitTemplate 方式2、Work Queues生产者消费者3、Publish/Subscribe关系绑定生产者消费者4、Routing消费者生产者5. Topics消费者生产者起步 七种模式 这七种模型其…

ch1_系统启动_bootsect.s

这里介绍&#xff0c; 操作系统启动中的 bootsect.S 文件 1. bootsect.s的作用概览 bootsect.s代码是磁盘引导块程序&#xff0c;驻留在磁盘的第一个扇区中(引导扇区&#xff0c;0磁道(柱面),0磁头&#xff0c;第一个扇区&#xff0c; 如图中的黄色区域&#xff09; 图一&…

springboot+jsp学生心理健康测评网

基于JSP技术设计并实现了学生心理健康网。该系统基于B/S即所谓浏览器/服务器模式&#xff0c;应用SSM框架&#xff0c;选择MySQL作为后台数据库。系统主要包括个人中心、用户管理、知识分类管理、知识信息管理、心理测试管理、交流论坛、试卷管理、系统管理、考试管理等功能模块…

Redis实战——优惠券秒杀(超卖问题)

1 实现优惠券秒杀功能 下单时需要判断两点&#xff1a;1.秒杀是否开始或者结束2.库存是否充足 所以&#xff0c;我们的业务逻辑如下 1. 通过优惠券id获取优惠券信息 2.判断秒杀是否开始&#xff0c;如果未返回错误信息 3.判断秒杀是否结束&#xff0c;如果已经结束返回错误…