1、HashMap底层实现数据结构?

总的来说,HashMap就是数组+链表的组合实现,每个数组元素存储一个链表的头结点,本质上来说是哈希表“拉链法”的实现。

HashMap的链表元素对应的是一个静态内部类Entry,Entry主要包含key,value,next三个元素

在Jdk1.8中HashMap的实现方式做了一些改变,但是基本思想还是没有变得,只是在一些地方做了优化,下面来看一下这些改变的地方,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,在性能上进一步得到提升。

2、 如何解决Hash冲突? put方法原理?

HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。当程序执行 map.put(String,Obect)方法 时,系统将调用String的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置

put方法分析:

public V put(K key, V value) {//调用putVal()方法完成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;//判断table是否初始化,否则初始化操作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 1sttreeifyBin(tab, hash);break;}//key存在,直接覆盖if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//记录修改次数++modCount;//判断是否需要扩容if (++size > threshold)resize();//空操作afterNodeInsertion(evict);return null;
}

下面将这个过程总结一下:

1、计算key的hash值,算出元素在底层数组中的下标位置。

2、通过下标位置定位到底层数组里的元素(也有可能是链表也有可能是树)。

3、取到元素,判断放入元素的key是否==或equals当前位置的key,成立则替换value值,返回旧值。

4、如果是树,循环树中的节点,判断放入元素的key是否==或equals节点的key,成立则替换树里的value,并返回旧值,不成立就添加到树里。

5、否则就顺着元素的链表结构循环节点,判断放入元素的key是否==或equals节点的key,成立则替换链表里value,并返回旧值,找不到就添加到链表的最后。

精简一下,判断放入HashMap中的元素要不要替换当前节点的元素,key满足以下两个条件即可替换:

1、hash值相等。

2、==或equals的结果为true。

3、 为什么String, Interger这样的类适合作为键?

String, Interger这样的类作为HashMap的键是再适合不过了,而且String最为常用。 
因为String对象是不可变的,而且已经重写了equals()和hashCode()方法了。

  1.不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。

2.因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
 

4、HashMap与HashTable的区别?

Hashtable可以看做是线程安全版的HashMap,两者几乎“等价”(当然还是有很多不同)。Hashtable几乎在每个方法上都加上synchronized(同步锁),实现线程安全。

区别
  1.HashMap继承于AbstractMap,而Hashtable继承于Dictionary; 
  2.线程安全不同。Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。而HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们额外的进行同步处理; 
  3.null值。HashMap的key、value都可以为null。Hashtable的key、value都不可以为null; 
  4.迭代器(Iterator)。HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException。 
  5.容量的初始值和增加方式都不一样:HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”。Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”; 
  6.添加key-value时的hash值算法不同:HashMap添加元素时,是使用自定义的哈希算法。Hashtable没有自定义哈希算法,而直接采用的key的hashCode()。 
  7.速度。由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

能否让HashMap同步? 
HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);

5、CurrentHashMap是如何实现并发的?

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁。

那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。

首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。

这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。

Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

JDK1.8中的实现

ConcurrentHashMap取消了segment分段锁,而采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

JDK1.8的ConcurrentHashMap的结构图如下:

TreeBin: 红黑二叉树节点
Node: 链表节点

【Java千问】HashMap之追命5连问相关推荐

  1. java 枚举获取key_「Java必修课」HashMap性能很好?问过我EnumMap没

    1 简介 我们知道Map只是一个接口,它有多种实现,Java中最常用的是HashMap了.而本文想讲述的是另一个实现:EnumMap.它是枚举类型的Map,要求它的Key值都必须是枚举型的. 2 创建 ...

  2. 面试HashMap之追命5连问

    1.HashMap底层实现数据结构? 总的来说,HashMap就是数组+链表的组合实现,每个数组元素存储一个链表的头结点,本质上来说是哈希表"拉链法"的实现. HashMap的链表 ...

  3. Java千百问局部变量、类变量、实例变量有什么区别

    Java千百问局部变量.类变量.实例变量有什么区别 局部变量.类变量.实例变量有什么区别 在聊局部变量.类变量.实例变量有什么区别之前,我们需要了解一下Java变量. 1.Java变量是什么 在数学世 ...

  4. new hashmap 初始大小_害怕面试被问HashMap?这一篇就搞定了

    来源于公众号编码之外 , 作者庆哥小白 话说以下这篇与本篇配合阅读最佳哦(jdk1.8为主) 搞定HashMap 作为一个Java从业者,面试的时候肯定会被问到过HashMap,因为对于HashMap ...

  5. [转]为什么Java中的HashMap默认加载因子是0.75

    前几天在一个群里看到有人讨论hashmap中的加载因子为什么是默认0.75. HashMap源码中的加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75 ...

  6. hashmap扩容_面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

  7. Java 8中HashMap冲突解决

    Java 8中HashMap冲突解决 目录(?)[+] 在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素.在最坏的情况下,这种方 ...

  8. Java 里的HashMap(HashTable) 简介.

    之前已经介绍过Java的另1个容器HashSet.  其实HashMap的存储原来跟HashSet区别不大, 可以说是HashSet的1个扩展. 一,预备知识: 哈希表 我们可以把哈希表看做是1个特别 ...

  9. java map套arraylist,在Java中的HashMap和ArrayList的区别?

    In Java, ArrayList and HashMap are used as collections. But I couldn't understand in which situation ...

最新文章

  1. Python爬虫案例-获取最新的中国行政区域划分
  2. WP8.1开发中复杂JSON绑定数据时的方法
  3. mybatis中statementHandler的设计与实现
  4. 酒店管理系统房态图的效果制作
  5. 编写自己的Javascript库-1
  6. java并发编程一:基础知识
  7. 矩阵模拟问题合集(Leetcode题解-Python语言)
  8. 字符设备驱动基础篇3——字符设备驱动工作原理
  9. LeetCode 1713. 得到子序列的最少操作次数(最长上升子序DP nlogn)
  10. 怎么把c语言改成汇编语言,如何把汇编语言转换成C语言
  11. 最大连续子数组和 动态规划_每日LeetCode,乘积最大子数组
  12. 让 Windows 的 R 用上 CUDA
  13. STM32利用Flymcu进行烧录
  14. VBB Arduino仿真软件的使用
  15. VMware10安装CentOS6.7
  16. java实训鉴定表_java实习自我鉴定材料定稿(资料4)
  17. 供应链业务架构设计概览
  18. 对虎牙直播进行爬取,并对信息进行处理分析
  19. 【客户是瞎子】腾讯微博注册的不友好提示
  20. IE浏览器无法添加进信任站点和受限站点,添加后被还原

热门文章

  1. matlab三维图形的绘制
  2. 应用层:电子邮件、MIME、SMTP协议、POP3协议
  3. Python 获取指定模块基址
  4. Wamp 提示Aestan Tray Menu服务未启动(非端口原因)
  5. 新店开张怎样屏蔽不良信息
  6. 百度地图技术赋能智能硬件 推动行业安全、智能化发展
  7. 【锋会现场】15岁少年极客黑入小米路由器
  8. ubuntu 安装jq工具
  9. hdu 4882 ZCC Loves Codefires(数学题+贪心)
  10. NetApp FAS2554故障灯常亮case处理过程分享