您的位置:首页 > 编程语言 > Java开发

Java集合系列之HashMap底层实现原理

2019-02-26 18:03 323 查看

HashMap特点及原理分析

特点
HashMap是java中使用最为频繁的map类型,其读写效率较高,但是因为其是非同步的,即读写等操作都是没有锁保护的,所以在多线程场景下是不安全的,容易出现数据不一致的问题。在单线程场景下非常推荐使用。

原理

HashMap的整体结构

  • 数据写入。以HashMap(String, String)为例,即对于每一个节点,其key值类型为String,value值类型也为String。在向哈希表中插入数据的时候,首先会计算出key值的hashCode,即key.hashCode()。该方法会返回一个32位的int类型的值,以int h = key.hashCode()为例。获取到h的值之后,会计算该key对应的哈希表中的数组的位置,计算方法就是取模运算,h%table.length。因为table的长度为2的整数次幂,所以可以用h与table.length-1直接进行位与运算,即是,index = h & (table.length-1)。得到的index就是放置新数据的位置。

    如果插入多条数据,则有可能最后计算出来的index是相同的,比如1和17,计算的index均为1。这时候出现了hash冲突。HashMap解决哈希冲突的方式,就是使用链表。每个链表,保存的是index相同的数据。

  • 数据读取。从哈希表中读取数据时候,先定位到对应的index,然后遍历对应位置的链表,找到key值和hashCode相同的节点,获取对应的value值。
  • 数据删除。 在hashMap中,数据删除的成本很低,直接定位到对应的index,然后遍历该链表,删除对应的节点。哈希表中数据的分布越均匀,则删除数据的效率越高(考虑到极端场景,数据均保存到了数组中,不存在链表,则复杂度为O(1))。
     

从构造方法中可以看到

  • 参数中的initialCapacity并不是哈希表的真实大小。真实的表大小,是不小于initialCapacity的2的整数次幂。
  • 哈希表的大小是存在上限的,就是2的30次幂。当哈希表的大小到达该数值时候,之后就不再进行扩容,只是向链表中插入数据了。

从PUT方法可以看到:

  • 给哈希表分配空间的动作,是向表中添加第一个元素触发的,并不是在哈希表初始化的时候进行的。
  • 如果对应index的数组值为null,即插入该index位置的第一个元素,则直接设置tab[i]的值即可。
  • 查看数组中index位置的node是否具有相同的key和hash如果有,则修改对应值即可。
  • 遍历数组中index位置的链表,如果找到了具有相同key和hash的node,跳出循环,进行value更新操作。否则遍历到链表的结尾,并在链表最后添加一个节点,将对应数据添加进去。

GET方法代码分析:

  • 先定位到数组中index位置,检查第一个节点是否满足要求 
  • 遍历对应该位置的链表,找到满足要求节点进行return

resize方法代码分析:

  • 如果就容量大于0,容量到达最大值,则不扩容。容量未到达最大值,则新容量和新门限翻倍。
  • 如果旧门限和旧容量均为0,则相当于初始化,设置对应的容量和门限,分配空间。
  • 旧数据的整理部分,非常非常的巧妙,先膜拜一下众位大神。在外层遍历node数组,对于每一个table[j],判断该node扩容之后,是属于低位部分(原数组),还是高位部分(扩容部分数组)。判断的方式就是位与旧数组的长度,如果为0则代表的是地位数组,因为index的值小于旧数组长度,位与的结果就是0;相反,如果不为零,则为高位部分数组。低位数组,添加到以loHead为头的链表中,高位数组添加到以hiHead为头的数组中。链表遍历结束,分别设置新哈希表的index位置和(index+旧表长度)位置的值。非常的巧妙。

    注意点
  • HashMap的操作中未进行锁保护,所以多线程场景下存取数据,很存在数据不一致的问题,不推荐使用
  • HashMap中key和value可以为null
  • 计算index的运算,h & (length - 1),感觉很巧妙,学习了
  • 哈希表的扩容中的数据整理逻辑,写的非常非常巧妙,大开眼界

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: