Java 学习笔记 —— HashMap
HashMap 作为 Java 中一个重要的知识点,经常出现在面试题中,跟随 Java 学习,因此我决定总结一下今天所学的 HashMap 内容。
什么是 HashMap
我们可以参考菜鸟教程给出的定义:
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map
接口,根据键的 HashCode
值存储数据,具有很快的访问速度,最多允许一条记录的键为 null
,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于 AbstractMap
,实现了 Map
、 Cloneable
、 java.io.Serializable
接口。
其实我们可以发现 HashMap 能够通过 key 以时间复杂度 O(1)
直接获取到对应的 value 值。
要想进一步了解更多有关 HashMap 的内容我们先刨析学习一下 HashMap 的结构。
HashMap 结构
我们都知道数组,数组的特点是存储区间连续的,内存占用严重。
优点是随机读取和修改效率高。 查询来说时间复杂度 O(1)
缺点是插入和删除速度慢。时间复杂度为 O(N)接下来链表的特点是存储区间是离散的。内存利用率高
优点是插入和删除速度快
缺点是不能随机查找。查询效率低而树的特点是本身具备了排序能力
看到了这些方法我们是不是有一个大人的想法那就是”小孩子才做选择,我全都要!“,综合起来我们便知道了 HashMap 的结构!
HashMap 就是采用了数组+链表+红黑树的结构。
通过分析得知可以通过 HashMap 中的 key
得到数组的索引,加快了 CRUD 的所有操作。
如何根据 key
得到索引呢?
答案:哈希算法:就是把任意长度值(key)通过散列算法转换成固定长度的 key(索引地址),通过这个索引地址完成数据的操作。
HashMap 的各种方法及使用
HashMap 的 Put 方法
put(k,v)
- 首先把
key
,value
封装成一个Node(HashMap的内部类 {hash,key,value,next})
对象。 -
底层调用
key
的hashCode
方法得到对应的 Hash 值。 -
通过哈希算法将 Hash 值转换成数组对应的索引值。如果索引值对应的位置没有任何元素则把
Node
对象放入到此索引位置,如果索引位置已经存在元素,就会拿着key
和链表中所有Node
对象的key
进行equals
比较。如果所有的key
都比较完毕返回false
则把新Node
放入到链表的尾部。如果在比价过程中有一个key
相同,则返回true
。那么这个Node
将会被新的Node
覆盖(相当于修改)。
HashMap 的 Get 方法
get(key)
- 底层调用
key
的hashCode
方法得到对应的 Hash 值。 -
通过哈希算法将 Hash 值转换成数组对应的索引值。
-
通过索引定位到链表。如果此位置没有任务数据则返回
null
,如果此位置存在一个单向链表则使用key
和链表中所有元素的key
进行比较。都不成功返回null
,成功则返回当前Node
。
HashMap 的 Remove 方法
remove(key,value)
HashMap
调用key
的hashCode
来计算 Hash 值,这个 Hash 值用来确定存储在 Hash 表中的位置。- 通过哈希算法将 Hash 值转换成 Hash 表中的索引位置。
- 如果在 Hash 表中的对应位置有多个映射到同一个索引,则发生了哈希冲突,那么就在这些
key
的链表或者树结构中遍历去寻找要删除的key
。 - 对比删除
key
和链表中的每个节点的key
,直到找到匹配的key
或者遍历全部,当找到匹配的key
时候,将相应的entry
(键值对)从链表或者树中删除。
HashMap 各种方式的使用
1.引入 HashMap 的包
import java.util.HashMap;
2.创建 HashMap 对象
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
//创建一个HashMap对象map,整型的key和String类型的value
3. put(key,value)
添加
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
//添加4个键值对
Maps.put(1,"Hutao");
Maps.put(2,"Frilyn");
Maps.put(3,"Dazai");
Maps.put(4,"Sakura");
//输出
System.out.println(Maps);
输出结果如下:
{1=Hutao, 2=Frilyn, 3=Dazai, 4=Sakura}
4. get(key)
获取/访问
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
Maps.put(1,"Hutao");
Maps.put(2,"Frilyn");
Maps.put(3,"Dazai");
Maps.put(4,"Sakura");
//输出
System.out.println(Maps.get(2));//获取key对应的value
System.out.println(Maps.get(3));
输出结果如下:
Frilyn
Dazai
5. remove(key)
删除
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
Maps.put(1,"Hutao");
Maps.put(2,"Frilyn");
Maps.put(3,"Dazai");
Maps.put(4,"Sakura");
//将key为4对应的键值对(key-value)删去
Maps.remove(4);
//输出
System.out.println(Maps);
输出结果如下:
{1=Hutao, 2=Frilyn, 3=Dazai}
6. size()
计算大小
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
Maps.put(1,"Hutao");
Maps.put(2,"Frilyn");
Maps.put(3,"Dazai");
Maps.put(4,"Sakura");
//获取元素数量并输出
System.out.println(Maps.size());
输出结果如下:
4
7. keySet()
返回 hashMap 中所有 key 组成的集合视图
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
Maps.put(1,"Hutao");
Maps.put(2,"Frilyn");
Maps.put(3,"Dazai");
Maps.put(4,"Sakura");
//返回所有 key 组成的 set 集合视图
System.out.println(Maps.keySet());
输出结果如下:
[1, 2, 3, 4]
8. getOrDefault(key,defaultValue)
获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
defaultValue
当指定的 key
不存在映射关系中时返回该默认值。
HashMap<Integer,String> Maps = new HashMap<Integer,String>();
Maps.put(1,"Hutao");
Maps.put(2,"Frilyn");
Maps.put(3,"Dazai");
Maps.put(4,"Sakura");
//key映射存在
String a = Maps.getOrDefault(1,"没找到");
System.out.println(a);
//key映射不存在
String b = Maps.getOrDefault(5,"没找到");
System.out.println(b);
输出结果如下:
Hutao
没找到
HashMap 还有许多方法这里暂时没有举例,引用菜鸟教程的常用方法列表:
方法 | 描述 |
---|---|
clear() | 删除 hashMap 中的所有键/值对 |
clone() | 复制一份 hashMap |
isEmpty() | 判断 hashMap 是否为空 |
putAll() | 将所有键/值对添加到 hashMap 中 |
putIfAbsent() | 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。 |
containsKey() | 检查 hashMap 中是否存在指定的 key 对应的映射关系。 |
containsValue() | 检查 hashMap 中是否存在指定的 value 对应的映射关系。 |
replace() | 替换 hashMap 中是指定的 key 对应的 value。 |
replaceAll() | 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。 |
forEach() | 对 hashMap 中的每个映射执行指定的操作。 |
entrySet() | 返回 hashMap 中所有映射项的集合集合视图。 |
values() | 返回 hashMap 中存在的所有 value 值。 |
merge() | 添加键值对到 hashMap 中 |
compute() | 对 hashMap 中指定 key 的值进行重新计算 |
computeIfAbsent() | 对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中 |
computeIfPresent() | 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。 |
HashMap 的一些知识点
Q:为什么效率快?
A:添加和修改功能都在链表上操作。而查询功能通过索引可以快速排除大部分数据,只在一部分链表上进行检索。
Q:为什么使用红黑树?
A:当链表长度过长的时候会出现查询速度慢的情况,链表在理论上来说可以无限延长。这时候就需要切换成红黑树。红黑树的特点就是插入慢(因为要判断小中大),查询速度快。
Q:HashMap 的扩容和转换
A:
扩容(数组): HashMap
默认的数组长度为 16.负载因子为 0.75(数组的长度达到容器 0.75 的时候就扩容)。每次扩容是原来的 2 倍。每次扩容后原来数组中的元素会依次重新计算并且插入。
转换(链表和红黑树): 链表长度大于 8 并且数组长度大于 64 则转换成红黑树。链表长度低于 6 的时候红黑树会转换成链表
HashMap 与 HashTable
HashTable 和 HashMap 的区别:
从用法角度来看是一样的,区别是以下几点:
HashMap
是线程不安全的。HashTable
是线程安全的HashMap
效率高,HashTable
效率低HashMap
中的key
和value
都可以为null
,HashTable
不可以为null
。
总结
HasdMap
是 Java
中常见且重要的内容,本文章写于 HashMap
学习后,经整理而写,由于对 HashMap
的掌握还不够深刻,全文并没有太深度的讲解底层原理以及一些高级应用的方法,属于一篇基础版,在攥写过程中内容主要参考了菜鸟教程。希望通过本文章能够帮助到你,感谢阅读。
Comments NOTHING