自己实现一个哈希表

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;}@Overridepublic 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);}@Overridepublic 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;}@Overridepublic 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);}@Overridepublic 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

HashMap(2)-----哈希表相关推荐

  1. Java数据结构和算法:HashMap,哈希表,哈希函数

    1. HashMap概述 HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的).此实现提供所有 ...

  2. HashMap - 基于哈希表和 Map 接口的键值对利器 (JDK 1.7)

    HashMap 的一些整理: (JDK 1.7) 基于哈希表的Map接口的非同步实现,定义了键映射到值的规则 此实现提供所有可选的映射操作,并允许使用null值和null键 此实现假定哈希函数将元素适 ...

  3. HashMap、哈希表、哈希函数

    1. HashMap概述 HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的).此实现提供所有 ...

  4. Hash+哈希表+HashMap+HashSet

    Hash+哈希表+HashMap+HashSet 哈希算法,是一类「算法」. 哈希表(Hash Table),是一种「数据结构」. 哈希函数,是支撑哈希表的一类「函数」. Map是映射/地图的意思,在 ...

  5. 哈希表的创建方式及用法

    创建哈希表 1.使用数组进行哈希表的创建 String [] hashTable = new String[4]; 2.使用hashMap创建哈希表 HasnMap<Integer,String ...

  6. 哈希表(HashMap)分析及实现(JAVA)

    转自:http://www.java3z.com/cwbwebhome/article/article8/83560.html?id=4649 ---------------------------- ...

  7. hashmap是散列表吗_一篇文章教你读懂哈希表-HashMap

    题图Pid=68670770 在最近的学习过程中,发现身边很多朋友对哈希表的原理和应用场景不甚了解,处于会用但不知道什么时候该用的状态,所以我找出了刚学习Java时写的HashMap实现,并以此为基础 ...

  8. java 静态 二维数组 转化hashmap_将一个二维数组转换为 hashmap 哈希表

    /** * 将一个二维数组转换为 hashmap * * 如果省略 $val 参数,则转换结果每一项为包含该项所有数据的数组. * * @param array $arr * @param strin ...

  9. java中哈希表怎么表示_java中HashMap概念是什么?怎么存取实现它?

    时代总是在不断的变化发展的,高新技术的应用也越来越普遍,大家对于新知识的渴望越来越强烈.java中很多的基础知识都是非常重要的.一起来看看关于HashMap的知识吧. 一. HashMap概述: Ha ...

最新文章

  1. 最强骨干网 ResNeSt 助力语义分割,ADE20K 全新 SOTA 47.6%
  2. 从算法到硬件,一文读懂2019年 AI如何演进
  3. windows下获取柱面、扇区数,扇区大小
  4. 自适应注意力机制在Image Caption中的应用
  5. ueditor 上传路径 Php_v9切换ueditor后图片上传路径问题 改成绝对路径
  6. SCI从入门到精通(二)——如何阅读文献
  7. arcface的前世今生
  8. android 动态磁贴,善用动态磁贴和Xbox游戏
  9. java如何解压rar文件怎么打开_java解压RAR压缩文件
  10. 从Q2财报看百度的转型三部曲
  11. 2021年职业院校技能大赛“网络安全”项目江西省A模块
  12. selenium click点击无反应问题
  13. Prompt-Tuning——深度解读一种新的微调范式
  14. php socket recv 超时,socket编程中的超时设置示例详解之一
  15. java实现日记软件_建立日记类
  16. 深度学习(二)---算法岗面试题
  17. python计算球的体积的函数设计
  18. 64位和32位程序性能差别
  19. 关于如何向老板提涨工资
  20. 汽车电子入门指南:总目录

热门文章

  1. python学习之中国地图shp文件下载与读取
  2. 秒 毫秒 微秒 纳秒 Hz KHz MHz GHz
  3. 环迅支付匠心独具,打造跨境收款解决新方式
  4. 怎么样可以调用阿里云短信服务接口实现短信验证码
  5. 鲁班H5页面生成工具
  6. 自定义 RPC框架4——RMI+Zookeeper实现RPC框架
  7. OpenCV VideoWriter报错: FFMPEG: tag ‘MP4V‘ is not supported with codec id 12 and format mp4解决方法
  8. 【001】Cortex-R5体系结构概述
  9. 吉时利万用表DMM6500
  10. 程序员DIY HIFI功放(前后级)的艰难过程