背景知识

哈希冲突

哈希是指通过某种方法把数据转变成特定的数值,数值根据mod对应到不同的单元上。比如在Java中,字符串就是通过每个字符的编码来计算、数字是本身对应的值等等,不过就算是再好的哈希方法,也有可能出现两个不同的对象hash值相同的情况。如果在HashMap中,hashcode相同,它们就会被分配到对应的存储位置,此时就会出现冲突——也叫做哈希冲突。

解决哈希冲突的方法有很多种:

  1. 开放地址探测法:即如果出现哈希冲突,则按照一定的规则继续选择位置,如线性探测法再、二次探测再、伪随机探测等等。
  2. 链地址法:如果出现冲突,则在冲突的位置后面形成链表进行存储。HashMap就是通过这种方式实现的
  3. 再哈希法:这种方法是再换另一个哈希方法寻找存储的位置。

hashCode和equals

首先hashcode是经过一定的方法映射出的数值,而equals如果没有重写的话,是对比了每个内部的属性。总结的来说,如果两个对象hashcode相同,它们未必相等;如果hashcode不同,肯定不等。从另一个角度说,如果两个对象equals相等,它们肯定相等;如果equals不同,则它们不同。

那么肯定会有人疑问,那还要hashcode干嘛咧?Hashcode其实就是在hashMap或者hashset进行快速比较的时候有用,可以快速的判断对像是否不同,如果hashcode相同,则再继续对比equals方法。这样可以节省大量的时间。

HashMap

HashMap允许null的key和value,HashMap根HashTable很像,只不过非线程安全并且允许Null值。

有两个参数会影响Map的性能,分别是初始容量initial capacity和负载参数load facotr(确定了什么时间增加hash table的容量)。当容量超过load factor*initial capacity时,就会进行扩容,然后执行rehash操作。

默认load factor时0.75,它基本已经能提供一个不错的性能效果了。不过在使用的初期可以预估一下数据量,直接设置一个比较适合的初始值。

注意:HashMap不是线程安全的,可以通过

Map m = Collections.synchronizedMap(new HashMap(...))

实现线程安全的map.

创建

transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
int threshold;
final float loadFactor;

新增

如果key之前出现过,那么将会用新的value代替旧的value

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

主要的代码在这里:

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);
// 如果每个链表长度超过8,那么就转为红黑树
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;
}

对于写操作的场景:

  1. 先会经过hash计算hashcode然后与size进行&操作,判断存储的位置
  2. 如果存储的位置没有节点,则直接写入
  3. 如果存储的位置有节点,且是树节点,则向树中插入节点
  4. 如果存储的位置有节点,不是树节点(而是普通的链表),则进行头插。但是会判断当前链表的长度,如果超过设置的阈值(默认是8),就会把链表转化成树。

更新的时候也是上面的操作流程,只不过在对比hashcode相同时,还会检查key是否equals

读取和删除基本上也是上面的套路。

为什么非线程安全

这个主要是因为在rehash的时候由于table[]后面接的是链表,而hashMap还是采用头插的形式。因此如果有不同的线程同时进行rehash,就可能导致链表形成环形,造成死循环。

具体的可以参考网上的文章:https://coolshell.cn/articles/9606.html

程序猿的日常——HashMap的相关知识的更多相关文章

  1. 程序猿的日常——SpringMVC系统架构与流程回顾

    web开发经历了很漫长的时间,在国内也快有十几年的时间了.从最开始的进程级到现在的MVC经历了很多的改进和优化,本篇就主要复习了解下Spring MVC相关的知识. 发展历程 第一阶段 CGI进程响应 ...

  2. 程序猿的日常——Java中的集合列表

    列表对于日常开发来说实在是太常见了,以至于很多开发者习惯性的用到数组,就来一个ArrayList,根本不做过多的思考.其实列表里面还是有很多玩法的,有时候玩不好,搞出来bug还得定位半天.所以这里就再 ...

  3. 程序猿的日常——Java基础之equals与hashCode

    equals和hashCode是我们日常开发最常使用的方法,但是因为一般都使用默认的规则,因此也很少会引起关注.不过了解他们的用途和设计的原则,还是会帮助我们更好的设计代码. equals equal ...

  4. 程序猿的日常——Java基础之clone、序列化、字符串、数组

    其实Java还有很多其他的基础知识,在日常工作技术撕逼中也是经常被讨论的问题. 深克隆与浅克隆 在Java中创建对象有两种方式: 一种是new操作符,它创建了一个新的对象,并把对应的各个字段初始化成默 ...

  5. 程序猿的日常——JVM内存模型与垃圾回收

    Java开发有个很基础的问题,虽然我们平时接触的不多,但是了解它却成为Java开发的必备基础--这就是JVM.在C++中我们需要手动申请内存然后释放内存,否则就会出现对象已经不再使用内存却仍被占用的情 ...

  6. 程序猿的日常——Mybatis现学现卖

    最近有一个小项目需求,需要用spring mvc + mybatis实现一个复杂的配置系统.其中遇到了很多不太常见的问题,在这里特意记录下: 主要涉及的内容有 事务 多表删除 插入并返回主键 1 sp ...

  7. 程序猿的日常——工作中常用的Shell脚本

    工作当中总是会有很多常用的linux或者命令,这里就做一个总结 文件远程拷贝 如果想把文件从本机拷贝到远程,或者从远程下载文件到本地. # 把本地的jar拷贝到远程机器xxxip的/home/sour ...

  8. 程序猿的日常——Java基础之抽象类与接口、枚举、泛型

    再次回顾这些基础内容,发现自己理解的又多了一点.对于一些之前很模糊的概念,渐渐的清晰起来. 抽象类与接口 抽象类通常是描述一些对象的通用方法和属性,并且默认实现一些功能,它不能被实例化.接口仅仅是描述 ...

  9. 毕业生、程序猿转岗该如何选择Java、大数据和VR?

    许久不见的朋友请我吃饭,期间给我介绍他一个弟弟,说明年要毕业了,还不知道找啥工作,说有培训机构让他学VR.大数据什么的,不知道前景咋样,想咨询一下我.相信很多朋友面临毕业,都不知道该从事哪个行业,自己 ...

随机推荐

  1. Loadrunner手动编写包含事务、检查点、关联等元素的脚本实例

    一.前言: 本文适合初学者,包含很多细节,包括 二.准备: 1.以虚拟机中的Linux系统作为服务器,开启bugfree服务. 2.以fiddler作为抓包工具,辅助脚本开发. 3.脚本流程:bugf ...

  2. 算法训练 K好数

      算法训练 K好数   时间限制:1.0s   内存限制:256.0MB 问题描述 如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数.求L位K进制数中K好数 ...

  3. springMVC使用jsp:include嵌入页面的两种方式

    1.静态嵌入子页面 <%@ include file="header.jsp" %>   静态嵌入支持 jsp . html . xml 以及纯文本. 静态嵌入在编译时 ...

  4. 对于vue.js初步了解

    由于本人做的是javaWeb的开发,对于前端的了解还是有限,今天对于vue.js了解了下(主要是看官方api),把自己的心得说一下,希望各位大神可以补充,谢谢   http://www.runoob. ...

  5. Jarvis OJ - [XMAN]level3 - Writeup——ret2libc尝试

    这次除了elf程序还附带一个动态链接库 先看一下,很一般的保护 思路分析 在ida中查看,可以确定通过read函数输入buf进行溢出,但是并没有看到合适的目标函数 但是用ida打开附带的链接库,可以看 ...

  6. EF(EntityFramework)与mysql使用,乱码问题

    1.中文乱码问题 利用ef更新数据到mysql数据库中,中文就会变成乱码"???",就算把mysql的数据库的编码设置为"utf8"也会变成乱码,从网上查询了下 ...

  7. 不用asp.net MVC,用WebForm照样能够实现MVC

    在<避开WebForm天坑,拥抱ASP.Net MVC吧>这篇博客中我讲到了ASP.net WebForm由于一些先天的"诱导犯罪"的缺陷,如今用ASP.net MVC ...

  8. 在windows平台使用Apache James搭建邮件服务器以及使用C#向外网发送邮件

    首先环境搭建: 1.下载安装JDK,并且配置环境变量 2.下载Apache James ,下载解压之后的目录如图 双击bin下边的run.bat批处理文件安装James 服务,提示如下信息说明安装成功 ...

  9. Anaconda+用conda创建python虚拟环境

    Anaconda+用conda创建python虚拟环境 Anaconda与conda区别 conda可以理解为一个工具,也是一个可执行命令,其核心功能是包管理与环境管理.包管理与pip的使用类似,环境 ...

  10. Swagger文档添加file上传参数写法

    想在swagger ui的yaml文档里面写一个文件上传的接口,找了半天不知道怎么写,终于搜到了,如下: /tools/upload: post: tags: - "tool" s ...