hashMap 底层原理+LinkedHashMap 底层原理+常见面试题
1.源码
java1.7 hashMap 底层实现是数组+链表
java1.8 对上面进行优化 数组+链表+红黑树
2.hashmap 是怎么保存数据的。
在hashmap 中有这样一个结构
Node implenets Map.entity{
hash
key
value
next
}
当我们像hashMap 中放入数据时,其实就是一个
Enity{
key
vaue
}
在存之前会把这个Entity 转成Node
怎么转的如下:
根据Entity 的key 通过hash 算出一个值 当成Node 的 hash ,key vlaue ,复制到Node 中,对于没有产生hash 冲突前,Node 的next 是null.
复制完成后,还需要通过Entity 对象的hash 算出 该 Entiry对象 具体应该放在 hashMap 的那个位置。计算如下 Hash&(lenth-1) 得到的值就是hashMap 对应具体的位置。(lentth是当前hashMap 的长度)。‘
解决hash 冲突
就是不同的元素通过 Hash&(lenth-1) 公式算出的位置相同,现在就启动了链表(单项链表),挂在了当前位置的下面,而链表的元素怎么关联呢,就用到了Node 的next ,next的值就是该链表下一个元素在内存中的地址。
jdk1.7 就是这样处理的,而到了 1.8 以后,就引用了红黑树,1.8以后这个链表只让挂7个元素,超过七个就会转成一个红黑树进行处理(最多是64,超多64 就会重新拆分)。
当红黑树下挂的节点小于等于6的时候,系统会把红黑树转成链表。 Node 在jdk1.8之前是插入l链表头部的,在jdk1.8中是插入链表的尾部的。
hashMap 扩容:
hashMap 会根据 当前的hashMap 的存储量达到 16*0.75=12 的时候,就是扩容 16*2=32 依次类推下去。2倍扩容。
扩容后元素是如何做到均匀分的。
对上面的总结:
LinkedHashMap 源码详细分析(JDK1.8)
这位大哥写的很好,可以看一下 https://segmentfault.com/a/1190000012964859
我针对LinkedHashMap 的总结有一下几点
1.LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构。该结构由数组和链表+红黑树 在此基础上LinkedHashMap 增加了一条双向链表,保持遍历顺序和插入顺序一致的问题。
2. 在实现上,LinkedHashMap 很多方法直接继承自 HashMap(比如put remove方法就是直接用的父类的),仅为维护双向链表覆写了部分方法(get()方法是重写的)。
3.LinkedHashMap使用的键值对节点是Entity 他继承了hashMap 的Node,并新增了两个引用,分别是 before 和 after。这两个引用的用途不难理解,也就是用于维护双向链表.
4.链表的建立过程是在插入键值对节点时开始的,初始情况下,让 LinkedHashMap 的 head 和 tail 引用同时指向新节点,链表就算建立起来了。随后不断有新节点插入,通过将新节点接在 tail 引用指向节点的后面,即可实现链表的更新
5.LinkedHashMap 允许使用null值和null键, 线程是不安全的,虽然底层使用了双线链表,但是增删相快了。因为他底层的Entity 保留了hashMap node 的next 属性。
6.如何实现迭代有序?
重新定义了数组中保存的元素Entry(继承于HashMap.node),该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。仍然保留next属性,所以既可像HashMap一样快速查找,
用next获取该链表下一个Entry,也可以通过双向链接,通过after完成所有数据的有序迭代.
7.竟然inkHashMap 的put 方法是直接调用父类hashMap的,但在 HashMap 中,put 方法插入的是 HashMap 内部类 Node 类型的节点,该类型的节点并不具备与 LinkedHashMap 内部类 Entry 及其子类型节点组成链表的能力。那么,LinkedHashMap 是怎样建立链表的呢?
虽然linkHashMap 调用的是hashMap中的put 方法,但是linkHashMap 重写了,了一部分方法,其中就有 newNode(int hash, K key, V value, Node<K,V> e)
linkNodeLast(LinkedHashMap.Entry<K,V> p)
这两个方法就是 第一个方法就是新建一个 linkHasnMap 的Entity 方法,而
linkNodeLast 方法就是为了把Entity 接在链表的尾部。
8.链表节点的删除过程
与插入操作一样,LinkedHashMap 删除操作相关的代码也是直接用父类的实现,但是LinkHashMap 重写了removeNode()方法
afterNodeRemoval
()方法,该removeNode方法在hashMap 删除的基础上有调用了afterNodeRemoval
回调方法。完成删除。
删除的过程并不复杂,上面这么多代码其实就做了三件事:
- 根据 hash 定位到桶位置
- 遍历链表或调用红黑树相关的删除方法
- 从 LinkedHashMap 维护的双链表中移除要删除的节点
TreeMap 和SortMap
1.TreeMap实现了SortedMap接口,保证了有序性。默认的排序是根据key值进行升序排序,也可以重写comparator方法来根据value进行排序具体取决于使用的构造方法。不允许有null值null键。TreeMap是线程不安全的。
2.TreeMap基于红黑树(Red-Black tree)实现。TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
public class SortedMapTest {
public static void main(String[] args) {
SortedMap<String,String> sortedMap = new TreeMap<String,String>();
sortedMap.put("1", "a");
sortedMap.put("5", "b");
sortedMap.put("2", "c");
sortedMap.put("4", "d");
sortedMap.put("3", "e");
Set<Entry<String, String>> entry2 = sortedMap.entrySet();
for(Entry<String, String> temp : entry2){
System.out.println("修改前 :sortedMap:"+temp.getKey()+" 值"+temp.getValue());
}
System.out.println("\n");
//这里将map.entrySet()转换成list
List<Map.Entry<String,String>> list =
new ArrayList<Map.Entry<String,String>>(entry2);
Collections.sort(list, new Comparator<Map.Entry<String,String>>(){
@Override
public int compare(Entry<String, String> o1, Entry<String, String> o2) {
// TODO Auto-generated method stub
return o1.getValue().compareTo(o2.getValue());
}
});
for(Map.Entry<String,String> temp :list){
System.out.println("修改后 :sortedMap:"+temp.getKey()+" 值"+temp.getValue());
}
}
}
附加点上面没有讲到的面试题:
1 HashMap特性?
HashMap的特性:HashMap存储键值对,实现快速存取数据;允许null键/值;线程不安全;不保证有序(比如插入的顺序)。
2 HashMap中hash函数怎么是是实现的?还有哪些 hash 的实现方式?
1. 对key的hashCode做hash操作(高16bit不变,低16bit和高16bit做了一个异或);
2. h & (length-1); //通过位操作得到下标index。
3. 扩展问题1:当两个对象的hashcode相同会发生什么?
因为两个对象的Hashcode相同,所以它们的bucket位置相同,会发生“碰撞”。HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。
4 扩展问题2:抛开 HashMap,hash 冲突有那些解决办法?
开放定址法、链地址法、再哈希法。
5如果两个键的hashcode相同,你如何获取值对象?
重点在于理解hashCode()与equals()。
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。两个键的hashcode相同会产生碰撞,则利用key.equals()方法去链表或树(java1.8)中去查找对应的节点。
6 针对 HashMap 中某个 Entry 链太长,查找的时间复杂度可能达到 O(n),怎么优化?
将链表转为红黑树,实现 O(logn) 时间复杂度内查找。JDK1.8 已经实现了。
7.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
扩容。这个过程也叫作rehashing,因为它重建内部数据结构,并调用hash方法找到新的bucket位置。
大致分两步:
1.扩容:容量扩充为原来的两倍(2 * table.length);
2.移动:对每个节点重新计算哈希值,重新计算每个元素在数组中的位置,将原来的元素移动到新的哈希表中。 (如何计算上面讲的有)
8 为什么String, Interger这样的类适合作为键?
String, Interger这样的类作为HashMap的键是再适合不过了,而且String最为常用。
因为String对象是不可变的,而且已经重写了equals()和hashCode()方法了。
1.不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。
注:String的不可变性可以看这篇文章《【java基础】浅析String》。
2.因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
9.hashmap.put 为什么是线程不安全的。(很重要)
正常情况下 hashmap 在保存数据时,底层是数组+链表+红黑树 但是 你去源码中看时,i发现子啊hashMap 底层没有加任何的多线程中的锁机制,比如: synchronize修饰 ,所以在多线程的情况下 hashMap 的单项链表,
可能会变成一个环形的链表,所以这个链表上的Next元素的指向永远不为null, 所以在遍历的时候就是死循环啊。
9.1HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的
9.2 HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。在hashmap做put操作的时候会调用到以上的方法。现在假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失
10 ,hashmap 初始化时就生了一个长度为16 的数组。
1.1 为什么初始化时16 而不是别的数字,
1.其实是4或者8 只要是2的N次幂就行,因为hashMap put 元素时,会根据key 进行运算得到一个位置,运算就是,根据key的hash值&hashMap的长度-1(hash&length-1) ,
假如hashMap的长度为16 补充:&运算时,都是1才为1,其他情况都为0
hash值 1010 1010 1000 0000 .... 1010
&
lennth-1 0111
你会发现不管hash值为多少,只要 length 的长度是2的N次幂, 那么length-1 的二进制最后一位就是1,所以 hash值&上length-1 最后得到的二进制数字的末位,可能是1 也可能是0,
如果 其长度不是2的n次幂,比如 15 ,那么15-1 =14 的 二进制 0110,那么遇上hash 的到二进制末位,永远就是0了 ,这就侧面的表明了通过计算出来的元素位置的分散性。
为什么不选4,8 这些也是2的N次幂作为扩容初始化值呢?其实8 也行4 也行,但是 我的java 是c语言写的,而c语言是由汇编语言,而汇编的语言来源是机器语言,而汇编的语言使用的就是16进制,对于经验而言,当然就选16喽。
hashMap 底层原理+LinkedHashMap 底层原理+常见面试题的更多相关文章
- Java集合详解4:一文读懂HashMap和HashTable的区别以及常见面试题
<Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...
- 小D课堂 - 新版本微服务springcloud+Docker教程_3-03CAP原理、常见面试题
笔记 3.分布式系统CAP原理常见面试题和注册中心选择 简介:讲解CAP原则在面试中回答和注册中心选择 C A 满足的情况下,P不能满足的原因: 数据同步(C) ...
- java常见面试题及答案
java常见面试题及答案 来源 https://blog.csdn.net/hsk256/article/details/49052293 来源 https://blog.csdn.net/hsk25 ...
- 【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!
本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.觉得内容不错 ...
- redis知识点及常见面试题
redis知识点及常见面试题 参考: https://zm8.sm-tc.cn/?src=l4uLj4zF0NCIiIjRnJGdk5CYjNGckJLQrIqNiZaJnpOWjIvQno2Llpy ...
- Java 常见面试题(一)
1)什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件.Java被设 ...
- Spring 常见面试题总结 | JavaGuide
首发于 JavaGuide 在线网站:Spring 常见面试题总结 最近在对 JavaGuide 的内容进行重构完善,同步一下最新更新,希望能够帮助你. Spring 基础 什么是 Spring 框架 ...
- Mybatis常见面试题
Mybatis常见面试题 #{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是 ...
- Java面试炼金系列 (1) | 关于String类的常见面试题剖析
Java面试炼金系列 (1) | 关于String类的常见面试题剖析 文章以及源代码已被收录到:https://github.com/mio4/Java-Gold 0x0 基础知识 1. '==' 运 ...
- java常见面试题及答案 1-10(基础篇)
java常见面试题及答案 1.什么是Java虚拟机?为什么Java被称作是"平台无关的编程语言"? Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程.Java 源文件被 ...
随机推荐
- Image Processing and Analysis_21_Scale Space:Scale-space theory A basic tool for analysing structures at different scales——1994
此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...
- c# 运算符和表达式
- Linux网络管理——nslookup
使用参考: https://www.computerhope.com/unix/unslooku.htm https://www.thegeekstuff.com/2012/02/dig-comman ...
- 挖矿病毒watchbog处理过程
1 挖矿病毒watchbog处理过程 简要说明 这段时间公司的生产服务器中了病毒watchbog,cpu动不动就是100%,查看cpu使用情况,发现很大一部分都是us,而且占100%左右的都是进程wa ...
- 用js刷剑指offer(替换空格)
题目描述 请实现一个函数,将一个字符串中的每个空格替换成“%20”.例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. 牛客网链接 js代码 func ...
- 复杂sql书写方法
给你一个复杂sql连接不同的表,多个嵌套查询条件等的语句时,你是非常的胆怯由于对语法的不熟悉以及没有经验和自信,现在我们来学习一下如何写复杂的sql,我们把它分解为很多小的步骤进行 一.集中最后的输出 ...
- GPU显存释放
一.当程序没有运行,但GPU仍被占用, 可通过nvidia-smi查看,被占用的pid是什么 或通过sudo fuser -v /dev/nvidia* #查找占用GPU资源的PID 然后采用kill ...
- [ 转载 ] Java基础二
前言 关于赢在面试的Java题系列基本收集整理完成了,所有题目都是经过精心挑选的,很基础又考验求职者的基本功,应该说被面试到的几率很大.这里整理挑选出来供大家面试前拿来看一看,所有题目整理自网络,有一 ...
- Spring-使用注解开发
8.使用注解开发 在Spring4之后,要使用注解开发,必须要保证AOP包已经导入了 使用注解需要导入context约束,增加注解的支持! <?xml version="1.0&quo ...
- mysql基础篇--删除
语法 truncate table 表名; #清空整个表的数据 delete from 表名 where 筛选条件; #按筛选条件删除数据 /* delete和truncate的区别 delete可以 ...