HashMap

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/hk10066/article/details/82837792

   
   
  1. title: HashMap
  2. date: 2018-09-24 11:33:31
  3. tags:HashMap

前一部分是拿的大佬的在这声明,后部分是自己看课程总结的

在这里贴上大佬的地址:https://www.jianshu.com/p/52066d6b7717

1.HashMap的实现原理

1,HashMap概述

HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选的映射操作, 并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变

2,HashMap的数据结构

  1. 类图结构

    <p><img alt="" class="has" height="225" src="https://img-blog.csdn.net/20180925120611895?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hrMTAwNjY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" width="491"></p>
    
    <p>&nbsp;</p>
    
    <p><strong>1.Map 接口:</strong> 定义将键值映射到值的对象,Map规定不能包含重复的键值,每个键最多可以映射一个值,这个接口是用来替换Dictionary类。</p>
    
    <p><strong>2.AbstractMap 类:</strong> 提供了一个Map骨架的实现,尽量减少了实现Map接口所需要的工作量</p>
    
    <p><strong>3.Cloneable 接口:</strong> 实现了该接口的类可以显示的调用Object.clone()方法,合法的对该类实例进行字段复制,如果没有实现Cloneable接口的实例上调用Obejct.clone()方法,会抛出CloneNotSupportException异常。正常情况下,实现了Cloneable接口的类会以公共方法重写Object.clone()</p>
    
    <p><strong>4.Serializable 接口:</strong> 实现了该接口标示了类可以被序列化和反序列化</p>
    </li>
    <li>
    <p><strong>构造方法:</strong></p>
    
    <p><strong>1.public HashMap(int initialCapacity, float loadFactor)</strong>:需要传入自定义的initialCapacity(初始化容量),loadFactor(加载因子)</p>
    
    <p><strong>2.public HashMap(int initialCapacity)</strong>:需要传入自定义的initialCapacity(初始化容量),实际在平时的使用过程中如果可以大概知道数据量,建议使用这种构造方法,原因是指定了HashMap的容量之后,可以避免没必要的扩容操作,从而减少了浪费。</p>
    
    <p><strong>3.public HashMap()</strong>:默认的构造方法,按照初始值创建HashMap</p>
    
    <p><strong>4.public HashMap(Map&lt;? extends K, ? extends V&gt; m)</strong>:需要传入一个Map集合</p>
    </li>
    
  • 在 java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引 用),所有的数据结构都可以用这两个基本结构来构造的,HashMap 也不例外。HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体

3.原理

  1. 内部结构

 

上图所示,是HashMap内部实现的结构图,正如介绍的其内部是个Node<K,V>类型的数组。数组中保存的有两种数据结构,第一种是链表,第二种是树形结构(使用的是红黑树)

从上图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表,当新建一个 HashMap 的时候,就会初始化一个数组。

  • ArrayList(查询快)

    <ul><li>
    	<p>数组实现</p>
    	</li>
    </ul></li>
    <li>
    <p><strong>LinkedList(插入和删除快)</strong></p>
    
    <ul><li>
    	<p>链表</p>
    	</li>
    </ul></li>
    <li>
    <p>Hash函数计算得到一个理想的——插入位置</p>
    </li>
    <li>
    <p><strong>源码猜测</strong></p>
    
    <ol><li>
    	<p>HashMap中的key value存储单元是什么</p>
    
    	<blockquote>
    	<p>static class Node&lt;K,V&gt; implements Map.Entry&lt;K,V&gt; { final int hash; final K key; V value; Node&lt;K,V&gt; next;</p>
    	</blockquote>
    	</li>
    	<li>
    	<p>数组该如何表示</p>
    
    	<blockquote>
    	<p>Node&lt;K,v&gt;[]</p>
    	</blockquote>
    	</li>
    	<li>
    	<p>数组的大小</p>
    
    	<blockquote>
    	<p>Defaultsize=初始大小</p>
    
    	<p>Maximumsiez=上限大小</p>
    	</blockquote>
    	</li>
    	<li>
    	<p>数组大小的解决方案</p>
    
    	<blockquote>
    	<p>final Node&lt;K,V&gt;[] resize() 扩容</p>
    
    	<p>扩容依据 负载因子</p>
    
    	<p>数组目前使用的大小 size</p>
    	</blockquote>
    	</li>
    	<li>
    	<p>链表的长度限制</p>
    
    	<blockquote>
    	<p><strong>在jdk1.8 如果链表的长度大于某个值,就将其结构改为红黑树</strong></p>
    	</blockquote>
    	</li>
    	<li>
    	<p>新来的Node节点放在哪里</p>
    
    	<blockquote>
    	<p>存在哪里需要有一个约束,就需要计算得来</p>
    
    	<p>Key value</p>
    
    	<p>利用这个key进行计算,计算这个Node到底放在哪里</p>
    
    	<p>Key.hashcode ——&gt;int 类型的值,得到了位置</p>
    
    	<p>49 这就是个合适的位置(49越界了)</p>
    
    	<p>Hash函数 hash(key) ——&gt; 得到理想的值</p>
    	</blockquote>
    	</li>
    </ol></li>
    <li>
    <p><strong>源码分析</strong></p>
    
    <ol><li>
    	<p>put——putVal()</p>
    	</li>
    	<li>
    	<p>putVal()</p>
    
    	<p>hash(Key)</p>
    	</li>
    	<li>
    	<p>因为要知道这个Key value 到底在HashMap结构的哪里</p>
    
    	<p>Hash函数</p>
    
    	<blockquote>
    	<p>(h = key.hashCode()) ^ (h &gt;&gt;&gt; 16);</p>
    	</blockquote>
    	</li>
    </ol></li>
    <li>
    <p>充分的将int类型的32为数全部应用起来</p>
    
    <p>高16位6^ 低16位</p>
    
    <blockquote>
    <p>DEFAULT_INITIAL_CAPACITY = 1 &lt;&lt; 4; // aka 16</p>
    </blockquote>
    
    <p>1&lt;&lt;4 位与运算 计算机识别更快</p>
    
    <blockquote>
    <p>Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, i;</p>
    
    <p>resize() 一个是初始化数组大小 扩容</p>
    
    <p>n 是记录数组大小的一个变量</p>
    
    <p>int threshold 容量X负载因子 16*0.75 12</p>
    
    <p>&nbsp;</p>
    
    <p>tab[i = (n - 1) &amp; hash] //tab[数值]确保数组不会越界</p>
    
    <p>数组的散列性就大,碰撞概率就低</p>
    
    <p>数组的大小一定要是2的n次幂</p>
    
    <p>if ((p = tab[i = (n - 1) &amp; hash]) == null)</p>
    
    <p>如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到 此数组中的该位置上。</p>
    </blockquote>
    
    <p><strong>put方法:</strong>put 方法比较经常使用的方法,主要功能是为HashMap对象添加一个Node 节点,如果Node存在则更新Node里面的内容</p>
    </li>
    <li>
    <p><strong>1)根据key计算当前Node的hash值,用于定位对象在HashMap数组的哪个节点。<br>
    2)判断table有没有初始化,如果没有初始化,则调用resize()方法为table初始化容量,以及threshold的值。<br>
    3)根据hash值定位该key 对应的数组索引,如果对应的数组索引位置无值,则调用newNode()方法,为该索引创建Node节点<br>
    4)如果根据hash值定位的数组索引有Node,并且Node中的key和需要新增的key相等,则将对应的value值更新。<br>
    5)如果在已有的table中根据hash找到Node,其中Node中的hash值和新增的hash相等,但是key值不相等的,那么创建新的Node,放到当前已存在的Node的链表尾部。<br>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果当前Node的长度大于8,则调用treeifyBin()方法扩大table数组的容量,或者将当前索引的所有Node节点变成TreeNode节点,变成TreeNode节点的原因是由于TreeNode节点组成的链表索引元素会快很多。<br>
    5)将当前的key-value 数量标识size自增,然后和threshold对比,如果大于threshold的值,则调用resize()方法,扩大当前HashMap对象的存储容量。<br>
    6)返回oldValue或者null。</strong><br>
    &nbsp;</p>
    </li>
    

4.HashMap的存取实现

  1. 存储:当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两 个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。

  2. 读取:从 HashMap 中 get 元素时,首先计算 key 的 hashCode,找到数组中对应 位置的某一元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的元素

  3. 小结

    <ul><li>
    	<p>HashMap 在底层将 key-value 当成一个整体进行处理,这个整体 就是一个 Entry 对象</p>
    	</li>
    	<li>
    	<p>HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当 需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个 Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该 Entry</p>
    	</li>
    </ul></li>
    

2.HashMap的一些说法

  1. HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap的底层结构是一个数组,数组中的每一项是一条链表。

  2. HashMap的实例有俩个参数影响其性能: “初始容量” 和 装载因子。

  3. HashMap中的key-value都是存储在Entry中的

  4. HashMap实现不同步,线程不安全

  5. HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性

  6. 解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap是采用拉链法解决哈希冲突的。 注: 链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位; 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。 拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值