接下来写的内容是看过众多大神的文章后,自己总结的一些心得体会,主要是为了让大家快速理解HashMap,应付面试百分百足矣。

当然讲HashMap之前必须先讲一些必备知识(等你回答完面试官问题,再深入问下去的时候,这些知识可以防止露馅):

map的定义

首先你要知道什么是map,map就是用于存储键值对(<key,value>)的集合类,也可以说是一组键值对的映射(数学概念)。注意,我这里说的只是map的概念,是为了通俗易懂,面试时候方便记忆,但是你自己一定要明白,在java中map是一个接口,是和collection接口同一等级的集合根接口。

map的存储结构

上边这个图就是map的存储结构,没错,看起来就像是数据库中的关系表,有两个字段(或者说属性),keyset(键的集合)和values(值的集合),每一条记录都是一个entry(一个键值对)。

Map的特点

1.没有重复的 key(一方面,key用set保存,所以key必须是唯一,无序的;另一方面,map的取值基本上是通过key来获取value,如果有两个相同的key,计算机将不知道到底获取哪个对应值;这时候有可能会问,那为什么我编程时候可以用put()方法传入两个key值相同的键值对?那是因为源码中,传入key值相同的键值对,将作为覆盖处理)

2.每个 key 只能对应一个 value, 多个 key 可以对应一个 value(这就是映射的概念,最经典的例子就是射箭,一排射手,一排箭靶,一个射手只能射中一个箭靶,而每个箭靶可能被不同射手射中。这里每个射手只有一根箭,不存在三箭齐发还都中靶这种骚操作。将射手和射中的靶子连线,这根线加射手加靶子就是一个映射)

3.key,value 都可以是任何引用类型(包括 null)的数据(只能是引用类型)

4.Map 取代了古老的 Dictionary 抽象类(知道就行,可以忽略)

哈希全家桶

把任意长度的输入(输入叫做预映射,知道就行),通过一种函数(hashCode() 方法),变换成固定长度的输出,该输出就是哈希值(hashCode),这种函数就叫做哈希函数,而计算哈希值的过程就叫做哈希。哈希的主要应用是哈希表和分布式缓存。

这里有个问题,哈希算法和哈希函数不是一个东西,哈希函数是哈希算法的一种实现,以后面试就说哈希函数就行。

在将键值对存入数组之前,将key通过哈希算法计算出哈希值,把哈希值作为数组下标,把该下标对应的位置作为键值对的存储位置,通过该方法建立的数组就叫做哈希表,而这个存储位置就叫做桶(bucket)。数组是通过整数下标直接访问元素,哈希表是通过字符串key直接访问元素,也就说哈希表是一种特殊的数组(关联数组),哈希表广泛应用于实现数据的快速查找(在map的key集合中,一旦存储的key的数量特别多,那么在要查找某个key的时候就会变得很麻烦,数组中的key需要挨个比较,哈希的出现,使得这样的比较次数大大减少。)

哈希表选用哈希函数计算哈希值时,可能不同的 key 会得到相同的结果,一个地址怎么存放多个数据呢?这就是哈希冲突(碰撞)。解决哈希冲突有两种方法,拉链法(链接法)和开放定址法(这种没用过)。拉链法:将键值对对象封装为一个node结点,新增了next指向,这样就可以将碰撞的结点链接成一条单链表,保存在该地址(数组位置)中。

正文

HashMap的定义

先从HashMap的定义开始,HashMap是用哈希表(直接一点可以说数组加单链表)+红黑树实现的map类。

HashMap的存储结构

上图便是HashMap的存储结构,HashMap的这种特殊存储结构在获取指定元素前需要把key经过哈希运算,得到目标元素在哈希表中的位置,然后再进行少量比较即可得到元素,这使得 HashMap 的查找效率极高。(说白了HashMap就是用了拉链法的哈希表,也有叫桶数组的)

烧脑部分

哈希函数计算结果越分散均匀,哈希碰撞的概率就越小,map的存取效率(时间复杂度)就会越高。

哈希表长度越长,空间成本越大,哈希函数计算结果越分散均匀。

扩容机制(实际上就是负载因子)和哈希函数越合理,空间成本越小,哈希函数计算结果越分散均匀。

从HashMap的默认构造函数源码可知,构造函数就是对下面几个字段进行初始化。

负载因子越大(长度一定),最大结点容量越大,resize次数越少,空间成本越小,map的存取效率就会越高。

桶数组初始容量(长度)越大(加载因子一定),最大结点容量越大,resize次数越少,空间成本越大,map的存取效率就会越高。

     int threshold;             // 最大node结点(键值对)容量,threshold = CAPACITY * LoadFactor,超过这个数目就重新resize(扩容),扩容后的threshold是之前的两倍。final float loadFactor;    // 加载因子(HashMap默认值是0.75,建议不要修改)int modCount;              // 记录HashMap内部结构发生变化的次数,强调一点,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。int size,CAPACITY;      // CAPACITY是桶数组的容量(桶的多少)(默认值是16),扩容后也是之前的两倍,size是HashMap中实际存在的键值对数量

这里存在一个问题,即使负载因子和哈希函数设计的再合理,也免不了会出现拉链过长(桶内结点过多)的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能

HashMap的特点

  • 底层实现是 链表数组,JDK 8 后又加了 红黑树
  • 实现了 Map 全部的方法
  • key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类(一般是String)需要重写 hashCode 和 equals 方法
  • 允许空键和空值(但空键只有一个,且放在第一位,知道就行)
  • 元素是无序的,而且顺序会不定时改变(每次扩容后,都会重新哈希,也就是key通过哈希函数计算后会得出与之前不同的哈希值,这就导致哈希表里的元素是没有顺序,会随时变化的,这是因为哈希函数与桶数组容量有关,每次结点到了临界值后,就会自动扩容,扩容后桶数组容量都会乘二,而key不变,那么哈希值一定会变)
  • 插入、获取的时间复杂度基本是 O(1)(前提是有适当的哈希函数,让元素分布在均匀的位置)
  • 遍历整个 Map 需要的时间与数组的长度成正比(因此初始化时 HashMap 的容量不宜太大)
  • 两个关键因子:初始容量、加载因子
  • HashMap不是同步,HashTable是同步的,但HashTable已经弃用,如果需要线程安全,可以用synchronizedMap,例如            Map m = Collections.synchronizedMap(new HashMap(...));

方法

遍历方法

1.可以采用keySet()+for循环的方法来遍历,keySet()返回的是一个Key值的集合

[html] view plain copy
  1. Map<String,String> map=new HashMap<String,String>();
  2. map.put("key1","value1");
  3. map.put("key2","value2");
  4. map.put("key3","value3");
  5. for(String key:map.keySet()){
  6. system.out.println("key:"+key);
  7. system.out.println("value:"+map.get(key));
  8. }

2.采用EntrySet()+Iterator进行遍历,EntrySet()返回的是一个Map.Entry的一个集合,它提供getKey(),getValue()方法来获取键值对。

[html] view plain copy
  1. Iterator< Map.Entry<String,String>> it=map.EntrySet().iterator();
  2. while(it.hasNext()){
  3. Map.Entry<String,String> entry=it.next();
  4. system.out.println("key:"+entry.getKey());
  5. system.out.println("value:"+entry.getValue());
  6. }

3.直接采用EntrySet+for增强进行遍历

[html] view plain copy
  1. for(Map.Entry<String,String> entry:map.entrySet()){
  2. system.out.println("key:"+entry.getKey());
  3. system.out.println("value:"+entry.getValue());
  4. }

4.取出所有value的值,但是不能取出KEY值

[html] view plain copy
  1. for(String value:map.values()){  <pre name="code" class="html">     system.out.println("value:"+value);

}

HashMap 的 4 个构造方法

//创建一个空的哈希表,初始容量为 16,加载因子为 0.75

public HashMap()

//创建一个空的哈希表,指定容量,使用默认的加载因子

public HashMap(int initialCapacity)

//创建一个空的哈希表,指定容量和加载因子

public HashMap(int initialCapacity, float loadFactor)

//创建一个内容为参数 m 的内容的哈希表

public HashMap(Map<? extends K, ? extends V> m)

添加方法

//添加指定的键值对到 Map 中,如果已经存在,就替换

public V put(K key, V value)

逻辑如下

  1. 先调用 hash() 方法计算哈希值
  2. 然后调用 putVal() 方法中根据哈希值进行相关操作
  3. 如果当前 哈希表内容为空,新建一个哈希表
  4. 如果要插入的桶中没有元素,新建个节点并放进去
  5. 否则从桶中第一个元素开始查找哈希值对应位置 
    1. 如果桶中第一个元素的哈希值和要添加的一样,替换,结束查找
    2. 如果第一个元素不一样,而且当前采用的还是 JDK 8 以后的树形节点,调用 putTreeVal() 进行插入
    3. 否则还是从传统的链表数组中查找、替换,结束查找
    4. 当这个桶内链表个数大于等于 8,就要调用 treeifyBin() 方法进行树形化
  6. 最后检查是否需要扩容

  • hash():计算对应的位置
  • resize():扩容
  • putTreeVal():树形节点的插入
  • treeifyBin():树形化容器

获取方法

public V get(Object key)

如果 HashMap 中包含一个键值对 k-v 满足:

(key == null ? k == null : key.equals(k))
  • 1
  • 2

就返回值 v,否则返回 null;

逻辑如下

  • 先计算哈希值;
  • 然后再用 (n - 1) & hash 计算出桶的位置;
  • 在桶里的链表进行遍历查找。

主要方法(map接口)

部分内容转自以下博客:

https://blog.csdn.net/qq_34149805/article/details/68174228

https://blog.csdn.net/u011240877/article/category/6447444

https://tech.meituan.com/java-hashmap.html

什么是HashMap?相关推荐

  1. 在js中使用HashMap数据结构,在js中使用K,V数据结构

    首先是定义一个HashMap方法,做基类(复制在js中即可,然后引用) //简单的哈希表,begin function HashMap() {/** Map 大小 * */var size = 0;/ ...

  2. Map再整理,从底层源码探究HashMap

    前言 本文为对Map集合的再一次整理.内容包括:Map HashMap LinkedHashMap TreeHashMap HashTable ConcurrentHashMap Map Map< ...

  3. HashMap 的长度为什么是 2 的幂次方?

    HashMap 的长度为什么是 2 的幂次方? 为了能让HashMap存取高效,尽量减少碰撞,需要将散列表的数据分配均匀.使用HashMap查询或插入数据时,需要先对数组长度取模运算,index = ...

  4. 解决Apache CXF 不支持传递java.sql.Timestamp和java.util.HashMap类型问题

    在项目中使用Apache开源的Services Framework CXF来发布WebService,CXF能够很简洁与Spring Framework 集成在一起,在发布WebService的过程中 ...

  5. hashmap value可以为空吗_美团面试题:Hashmap结构,1.7和1.8有哪些区别(最详细解析)...

    作者|依本多情 原文:blog.csdn.net/qq_36520235/article/details/82417949 一.真实面试题之:Hashmap的结构,1.7和1.8有哪些区别 不同点: ...

  6. hashmap实现原理_Java中HashMap底层实现原理(JDK1.8)源码分析

    在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依 ...

  7. android 设置setmultichoiceitems设置初始化勾选_阿里巴巴Java开发手册建议创建HashMap时设置初始化容量,但是多少合适呢?...

    集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生. 关于HashMap,很多人都对他有一些基本的了解,比如他和hashtab ...

  8. HashMap 和 Hashtable 的 6 个区别,最后一个没几个人知道!

    HashMap 和 Hashtable 是 Java 开发程序员必须要掌握的,也是在各种 Java 面试场合中必须会问到的. 但你对这两者的区别了解有多少呢? 现在,栈长我给大家总结一下,或许有你不明 ...

  9. transient HashMap使用目的分析

    看HashSet源码有这么一句: private transient HashMap<E,Object> map; 再看HashSet的Add方法: 实际上HashSet是复用HashMa ...

  10. 调试JDK源码-一步一步看HashMap怎么Hash和扩容

    调试JDK源码-一步一步看HashMap怎么Hash和扩容 调试JDK源码-ConcurrentHashMap实现原理 调试JDK源码-HashSet实现原理 调试JDK源码-调试JDK源码-Hash ...

最新文章

  1. VTK:可视化之CameraModel1
  2. perl xml dom中文乱码问题解决
  3. java删除文件夹的所有文件
  4. mysql5.5安装配置 在阿里云服务器上 本地navicat连接
  5. 【POJ - 3126】Prime Path(bfs)
  6. Trie树讲解(例题:ACWING 835,ACWING 143)
  7. 【静态网页制作大作业——个人博客搭建(HTML+CSS+Javascript)】
  8. 现金支票打印模板excel_施工表格填写不规范?500套最全施工资料表格模板,可直接套用|面层|分项...
  9. MP3比特率编码模式
  10. 光学动作捕捉系统构成
  11. 战神引擎传奇手游源码【诛仙玛法单职业五大陆】
  12. 阿里正式交棒在即 普通股“一拆八”为赴港上市铺路?
  13. 2017多校第4场 HDU 6078 Wavel Sequence DP
  14. MSI_MSI-X中断之体验与使用
  15. 计算机图形输入的原理,【计算机图形学】零 · 计算机图形系统概述
  16. linux yum安装mysql及配置
  17. Apple Watch如何重新配对
  18. BSF深度搜索时到底是如何回溯的(小tip)
  19. Python中turtle库常用函数
  20. kcp 介绍与源代码分析_KCP-GO源码解析

热门文章

  1. 稀疏表示 河流多元信息处理 matlab
  2. 公众号开发精品教程(2)——将项目接入微信及简单交互
  3. 保姆级在自己电脑搭建我的世界服务器教程
  4. Openwrt Lede koolshare固件下屏蔽固定MAC地址以及屏蔽某些网站
  5. 日本语研究法1-10合集
  6. 家里有宽带还能再装一条吗_家里一条宽带可以装两个无线路由器吗?要怎么安装?怎么设置?...
  7. 几种媒体文件(MediaPlayer,Realplay,QuickTime)的调用代码
  8. 程序员趣味儿逻辑测题-2(附答案)这次的题稍难,快来挑战吧!
  9. 【工具】60 个相见恨晚的神器工具
  10. C++代码:小明存钱