当前位置: 首页 > 科技观察

重温经典数据结构:HashCode和HashMap原理

时间:2023-03-20 10:20:31 科技观察

一、HashCode为什么用31作为乘数1、选择31是因为它是奇质数。如果选择偶数,乘法运算会发生溢出,导致数值信息丢失,因为乘以2相当于移位运算。选择质数的优势并不是特别明显,但这是一种传统。2.数字31有一个很好的特性,就是乘法运算可以用移位和减法运算代替,以获得更好的性能:31*i==(i<<5)-i,现代Java虚拟机可以这样优化自动完成。二、数组和链表的特点数组的特点是:寻址容易,插入删除困难。链表的特点是:寻址困难,插入删除方便。3、HashMap的原理是允许空键和空值,空键的值为0。HashMap不保证键值对的顺序,键值对的顺序在运行过程中会发生变化。HashMap是一个非线程安全的类,在多线程环境下会出现问题。HashMap是在JDK1.2引入的,底层是基于哈希算法的。JDK1.8涉及的很多:1.哈希表实现,2.扰动函数,3.初始化容量,4.负载因子,5.扩展元素分裂,6.链表树,7.红黑树,8.insert,9,find,10,delete,11,traverse,12,segmentlock等(1)扰动函数staticfinalinthash(Objectkey){inth;返回(键==空)?0:(h=key.hashCode())^(h>>>16);}扰动函数是为了增加随机性,使数据元素散列更均匀,减少碰撞。(2)负载因子staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;loadfactor可以理解为当一辆汽车能承受的超过一定的阈值,就把货物放到新车上。在HashMap中,加载因子决定了后面要扩容多少数据。0.75是默认的构造值,创建HashMap时也可以调整。如果想用更多的空间换取时间,可以将loadfactor调小一些,减少碰撞。(3)展开元素拆分展开最直接的问题是需要将元素拆分成新的数组。在JDK1.7中会重新计算hash值,但在JDK1.8之后进行了优化。newnewCap和newThr是扩容时计算的,是两个词的缩写,一个是Capacity,一个是valveThreshold。newCap用于创新数组bucketnewNode[newCap]。随着扩容,原来链表和红黑树中存储的元素因为hash冲突,需要拆分存储到新的位置。(4)数据迁移(5)数据插入先对哈希值进行扰动,得到新的哈希值。(键==空)?0:(h=key.hashCode())^(h>>>16).判断tab是否为空或者长度为0,如果是则进行扩容操作。if((tab=table)==null||(n=tab.length)==0)n=(tab=resize())。长度。下标是根据哈希值计算的。如果对应的下标没有存储数据,可以直接插入,否则需要覆盖。tab[i=(n-1)&hash]).判断tab[i]是否为树节点,否则向链表插入数据,否则向树插入一个节点。如果向链表插入节点时链表长度大于等于8,则需要将链表转为红黑树。treeifyBin(标签,散列)。最后,处理完所有元素后,判断是否超过阈值;阈值,超过则扩容。treeifyBin是一个将链表转为树的方法,但并不是所有长度为8的链表都会转为树。还需要判断存储key值的数组bucket的长度是否小于64MIN_TREEIFY_CAPACITY。如果小于该值,则需要对其进行扩展。扩容后,链表上的数据会被拆分分布在相应的桶节点上,链表的长度会缩短。(6)链表树链表树有两种情况;链表长度大于等于8,桶容量大于64,否则只会扩容,不会被树化。在对链表进行树化的过程中,首先将链表转化为树节点,此时的树可能不是平衡树。同时在树转换过程中会记录链表的顺序,tl.next=p,主要是为了后续树转链表和分裂方便。完成链表到树的转换后,再进行红黑树的转换。首先简单介绍一下,红黑树转换需要着色和旋转,还有大小比较。在比较元素大小的时候,有一个比较有意思的方法,tieBreakOrderOT,主要是因为HashMap本身没有像TreeMap那样的Comparator实现。(7)在红黑树转链的过程中,记录了原链表的顺序。红黑树转链表时,直接将TreeNode转为Node。因为记录了链表的关系,所以替换过程非常容易。(8)寻找扰动函数的用途,得到新的哈希值。下标计算,tab[(n-1)&hash])。确定了桶数组的下标位置之后,接下来就是搜索遍历红黑树和链表。(9)删除删除操作也比较简单,没有太多复杂的逻辑。另外,由于对红黑树的操作进行了封装,所以也非常好用。(10)遍历这里我们需要设置一个既有红黑树又有链表结构的数据场景。为了有这样的数据结构,我们最好把HashMap的初始长度设置为64,避免链表超过8位后扩容,而是直接转成红黑树。找到18个元素,将它们放在不同的节点上(这些数据是程序计算出来的)。桶阵列02节点:24、46、68。桶阵列07节点:29。桶数组12个节点:150、172、194、271、293??、370、392、491、590。测试代码publicvoidtest_Iterator(){Mapmap=newHashMap(64);map.put("24","Idx:2");map.put("46","Idx:2");map.put("68","Idx:2");map.put("29","Idx:7");map.put("150","Idx:12");map.put("172","Idx:12");map.put("194","Idx:12");map.put("271","Idx:12");System.out.println("排序01:");for(Stringkey:map.keySet()){System.out.print(key+"");}map.put("293","Idx:12");map.put("370","Idx:12");map.put("392","Idx:12");map.put("491","Idx:12");map.put("590","Idx:12");System.out.println("\n\nsort02:");for(Stringkey:map.keySet()){System.out.print(key+"");}map.remove("293");map.remove("370");map.remove("392");map.remove("491");map.remove("590");System.out.println("\n\n排序03:");for(Stringkey:map.keySet()){System.out.print(key+"");}}添加元素,当HashMap仍只是一个链表结构时,输出测试结果01添加元素,当HashMap转为红黑树时,输出测试结果02。删除元素,当HashMap转为链表结构时,输出测试结果03。结果如下:Sort01:24466829150172194271Sort02:24466829271150172194293370392491590Sort03:24466829172271150Processitfinishedwithex10API源码和逻辑结合上一篇数据结构中的扰动函数、负载因子、哈希表实现等内容,可以看作是对HashMap基本常用技术点的补全。但知识并不止于此。还有红黑树的相关技术内容,后面会详细介绍。除了HashMap,还有TreeMap、ConcurrentHashMap等,每个核心类都有与之相关的核心知识点,每一个都值得深入学习。这种烧脑的过程是学习所学知识的最好方式。