map-HashMap
HashMap
图片~~~
其他常见的map结构
常见的map结构
常用的Map结构有:hashMap(最常用)、hashTable、LinkedHashMap、TreeMap(对存入的键值进行排序)
LinkedHashMap和HashMap的区别
LinkedHashmap继承自hashMap,基于hashMap和双向链表实现
LinkedHashMap有序(插入有序和访问有序----默认为插入有序),hashMap则是无序
LinkedHashMap和hashMap都是基本entry[]存取数据
LinkedHashMap线程不安全
————————————————
需要的知识
hash和hashCode()
hashcode相等对象不一定相等,所以还要比较key的值
hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
hashCode 的常规协定是:
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
参考自~Java中hashCode的作用
一、HashMap简单介绍
1.8之前 (头插法)
数组是主体,链表的引入是当hashcode发生冲突的时候引入的
1.8之后(尾插法)
尾插法参考~HashMap的为啥用尾插法?
当主体长度大于64,链表长度大于8的时候(都要满足)
将链表转换为红黑树(平衡二叉树) 他都是基于效率来的
因为他们开发人员发现,即使链表长度大于8但是数组长度小于64的时候,转换为红黑树后遍历的效率并不会提升,反而会更麻烦
为什么 要加入红黑树?
本身设计的初衷就是为了更好的效率
而红黑树比链表查询速度快
链表的时间复杂度 n 红黑树 logn
特点:
- 存取无序的
- 键和值位置都可以是null,但是键位置只能是—个null
- 键位置是唯一的,底层的数据结构控制键的
- jdk1.8前数据结构是:链表+数组jdk1.8之后是:链表+数组+红黑树
- 阈值(边界值)>8并且数组长度大于64,才将链表转换为红黑树,变为红黑树的目的是为了高效的查询
二、HashMap集合底层数据结构


通过将value转换成hashcode
hashCode = key.hashCode()
再使用算法将其作为数组下标把值放入
index = key.hashCode() & 桶.length
索引计算 HashMap 中 key 的存储索引是怎么计算的?先得到HashMap主体的长度length, 首先根据key的值计算出hashcode的值,然后根据hashcode计算出hash值,最后通过hash&(length-1)计算得到存储的位置。
hashcode是计算hash值的方法、返回对象的哈希码值。
hashcode相同但key不一定相同,可能存在不同的key映射到同一个位置
- .size表示 HashMap中K-V的实时数量,注意这个不等于数组的长度
- threshold(临界值)=capacity(容量)*loadFactor(加载因子)。这个值是当前已占用数组长度的最大值。size超过这个临界值就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。
- 所以size的长度永远小于桶大小的0.75
数据结构就是一种存储数据的结构
存储数据
在存储value1的时候,如果计算出的索引值为a,且此时的数组空间也存在索引为a的值value2,那么就会对已存在的值的hash值进行比较,若不同则在此空间上创建一个节点来存储value1
如果hash值相同,则发生哈希碰撞
底层就会调用key所属类的equals方法来判断内容是否相等
相等:覆盖
不相等:继续向下和其他数据的key比较,都不相等则在该空间画出一个新节点存储数据
三、底层源码
主要方法 putVal()
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/* 关键 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
tab就是hashmap的主体 - 数组
HashMap将链表转换为红黑树treeifyBin()方法
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// 扩容 --------------------------
resize();
// 这一块儿就是转换成红黑树-----------------
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
面试题
1.哈希表底层如何计算hash值?还有哪些算法可以计算出hash值?
底层采用的key的hashCode方法的值结合数组长度进行无符号右移(>>>)、按位异或(^)、按位与(&)计算出索引。(效率高)
还可以采用:平方取中法,取余数,伪随机数法(效率较低)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2.面试题:当两个对象的hashCode相等时会怎么样?
会产生哈希碰撞,若key值内容相同则替换旧的value.不然连接到链表后面,链表长度超过阈值8就转换为红黑树存储。
3.面试题:何时发生哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞?
只要两个元素的key计算的哈希码值相同就会发生哈希碰撞。jdk8前使用链表解决哈希碰撞。jdk8之后使用链表+红黑树解决哈希碰撞。
4.面试题:如果两个键的hashcode相同,如何存储键值对?
hashcode相同,通过equals比较内容是否相同。
相同:则新的value覆盖之前的value
不相同:则将新的键值对添加到哈希表中
5.扩容问题
在不断的添加数据的过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
6.何时变成红黑树
通过上述描述,当位于一个链表中的元素较多,即hash值相等但是内容不相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度(阀值)超过8时且当前数组的长度>64时,将链表转换为红黑树,这样大大减少了查找时间。jdk8在哈希表中引入红黑树的原因只是为了查找效率更高。
简单的来说,哈希表是由数组+链表+红黑树(IDK1.8增加了红黑树部分)实现的。
三、HashMap的继承关系
说明:
Cloneable空接口,表示可以克隆。创建并返回HashMap对象的一个副本。
.Serializable序列化接口。属于标记性接口。HashMap对象可以被序列化和反序列化。
.AbstractMap 父类提供了Map实现接口。以最大限度地减少实现此接口所需的工作。
补充:通过上述继承关系我们发现一个很奇怪的现象,就是HashMap已经继承了AbstractMap而AbstractMap类实现了Map接口,那为什么HashMap还要在实现Map接口呢?同样在ArrayList中LinkedList中都是这种结构。
据java集合框架的创始人Josh Bloch描述,这样的写法是一个失误。在java集合框架中,类似这样的写法很多,最开始写java集合框架的时候,他认为这样写,在某些地方可能是有价值的,直到他意识到错了。显然的,JDK的维护者,后来不认为这个小小的失误值得去修改,所以就这样存在下来了。
map-HashMap的更多相关文章
- Collections+Iterator 接口 | Map+HashMap+HashTable+TreeMap |
Collections+Iterator 接口 1. Collections 是一个操作 Set.List 和 Map 等集合的工具类 Collections 中提供了大量方法对集合元素进行排序.查询 ...
- ES6 & Map & hashMap
ES6 & Map & hashMap 01 two-sum https://leetcode.com/submissions/detail/141732589/ hashMap ht ...
- Map HashMap 排序 迭代循环 修改值
HashMap dgzhMap = Dict.getDict("dgzh"); Iterator it_d = dgzhMap.entrySet().iterator(); whi ...
- Map随笔:最常用的Map——HashMap
目录 Map随笔:最常用的Map--HashMap 前言: 1,HashMap的结构 2,HashMap的一些属性(JDK8) 3,HashMap的构造函数(JDK8) 4,HashMap的一些方法( ...
- [Java] Map / HashMap - 源代码学习笔记
Map 1. 用于关联 key 和 value 的对象,其中 key 与 key 之间不能重复. 2. 是一个接口,用来代替 Java 早期版本中的 Dictionary 抽象类. 3. 提供三种不同 ...
- 高并发第九弹:逃不掉的Map --> HashMap,TreeMap,ConcurrentHashMap
平时大家都会经常使用到 Map,面试的时候又经常会遇到问Map的,其中主要就是 ConcurrentHashMap,在说ConcurrentHashMap.我们还是先看一下, 其他两个基础的 Map ...
- Map / HashMap 获取Key值的方法
方法1:keySet()HashMap hashmp = ne HashMap();hashmp.put("aa", "111");Set set = hash ...
- Map:HashMap和TreeMap
一.Map集合 特点:将键映射到值得对象 Map集合和Collection集合的区别? Collection:是单列集合,存储的是单独出现的元素 Map: 是双列集合,存储的是键值对形式 ...
- Java集合 之Map(HashMap、Hashtable 、TreeMap、WeakHashMap )理解(new)
HashMap 说明: 在详细介绍HashMap的代码之前,我们需要了解:HashMap就是一个散列表,它是通过“拉链法”解决哈希冲突的.还需要再补充说明的一点是影响HashMap性能的有两个参数:初 ...
- golang 多维哈希(map,hashmap)实践随笔
有些场景使用多维哈希来存储数据,时间复杂度恒定,简单粗暴好用.这里记录一下. 如下是三维哈希的简单示意图,建议层数不要太多,否则时间久了,自己写的代码都不认识. 下图是三维哈希在内存的存储形式,has ...
随机推荐
- MAMP使用简单教程
这个配置,没有域名访问,平时可以放些demo使用,如果需要域名访问请看MAMP PRO教程 启用服务 打开Launchpad中灰色的MAMP,进入界面后,点击Preferences,然后只需拿着鼠标点 ...
- 深入理解CPU的调度原理
前言 软件工程师们总习惯把OS(Operating System,操作系统)当成是一个非常值得信赖的管家,我们只管把程序托管到OS上运行,却很少深入了解操作系统的运行原理.确实,OS作为一个通用的软件 ...
- 查看nginx版本号的几种方法
1. 查看服务器上安装的nginx版本号,主要是通过nginx的-v或-V选项,查看方法如下图所示 -v 显示 nginx 的版本. -V 显示 nginx 的版本,编译器版本和配 ...
- python-argparse用法简介
1. argparse介绍 argparse是Python标准库中用于解析命令行参数的模块.它提供了一种简洁而灵活的方式来处理命令行参数,包括选项(可选参数)和位置参数(必需参数) 2. argpar ...
- 基于.NetCore开发 StarBlog 番外篇 (2) 深入解析Markdig源码,优化ToC标题提取和文章目录树生成逻辑
前言 虽然现在工作重心以AI为主了,不过相比起各种大模型的宏大叙事,我还是更喜欢自己构思功能.写代码,享受解决问题和发布上线的过程. 之前 StarBlog 系列更新的时候我也有提到,随着功能更新,会 ...
- [每日算法 - 华为机试] leetcode463. 岛屿的周长
入口 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer.https://le ...
- SQL Server 2008语句大全完整版
--======================== --设置内存选项 --======================== --设置 min server memory 配置项 EXEC sp_co ...
- [T.4] 团队项目:团队代码管理准备
团队的代码仓库地址 [GitHub - Meng-XuanYu/JayJay-TeamVersionControl: A public repo for BUAASE2025 course homew ...
- 通过 Python 在PDF中添加、或删除超链接
PDF文件现已成为文档存储和分发的首选格式.然而,PDF文件的静态特性有时会限制其交互性.超链接是提高PDF文件互动性和用户体验的关键元素.Python作为一种强大的编程语言,拥有多种库和工具来处理P ...
- Web前端入门第 42 问:聊聊 CSS 元素上下左右(水平+垂直)同时居中有几种方法
影响元素位置的 CSS 属性基本介绍完毕(参考前几篇文章),现思考一个最常见的需求: 一个子元素,要摆放在盒子的正中央,使用 CSS 布局手段,究竟有多少种实现方式? 上下左右(水平方向.垂直方向)要 ...