HashMap是我们最常用的集合之一,同时Java8也提升了HashMap的性能。本着学习的原则,在这探讨一下HashMap。

原理

简单讲解下HashMap的原理:HashMap基于Hash算法,我们通过put(key,value)存储,get(key)来获取。当传入key时,HashMap会根据key.hashCode()计算出hash值,根据hash值将value保存在bucket里。当计算出的hash值相同时怎么办呢,我们称之为Hash冲突,HashMap的做法是用链表和红黑树存储相同hash值的value。当Hash冲突的个数比较少时,使用链表,否则使用红黑树。

数据结构

一图胜千言:

我们可以在HashMap的源码中找到这样一句:

transient Node<K,V>[] table;

很明显,HashMap还是凭借数组实现的,辅以链表和红黑树。我们知道数组的特点:寻址容易,插入和删除困难,而链表的特点是:寻址困难,插入和删除容易,红黑树则对插入时间、删除时间和查找时间提供了最好可能的最坏情况担保。HashpMap将这三者结合在一起。

Hash算法

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

如果你也看过7之前的Hash算法,会发现这个版本的算法比之前的简洁。

重要的内部类

Node

 static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; } public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}

链表节点,存储键值对,并含有一个next引用。

TreeNode

 static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
} /**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
} /**
* Ensures that the given root is the first node of its bin.
*/
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) {
Node<K,V> rn;
tab[index] = root;
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}

红黑树的节点

重要方法

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;
}

这是HashMap中的put函数,里面的参数boolean onlyIfAbsent,boolean evict我并不知道有什么用,因为put在调用的时候,是将这两个参数写死了,若知道请告知:

 public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

另外我们可以看到,当节点个数>= TREEIFY_THRESHOLD - 1时,HashMap将采用红黑树存储。为什么这么做呢?正如我们前面提到的,当发生Hash冲突时,HashMap首先是采用链表将重复的值串起来,并将最后放入的值置于链首,java8对HashMap进行了优化。当节点个数多了之后使用红黑树存储。这样做的好处是,最坏的情况下即所有的key都Hash冲突,采用链表的话查找时间为O(n),而采用红黑树为O(logn),这也是Java8中HashMap性能提升的奥秘,详细的测试可以看这篇博文

总结

这篇文章简单介绍了下Java8中的HashMap中的数据结构,Hash算法,内部类,简单分析了Java8中性能提升的奥秘,由于水平原因难免会出现一些纰漏,希望各位能即时纠正。

https://segmentfault.com/a/1190000003016453

初探Java8中的HashMap(转)的更多相关文章

  1. Java8中的HashMap分析

    本篇文章是网上多篇文章的精华的总结,结合自己看源代码的一些感悟,其中线程安全性和性能测试部分并未做实践测试,直接是“拿来”网上的博客的. 哈希表概述 哈希表本质上一个数组,数组中每一个元素称为一个箱子 ...

  2. Java7与Java8中的HashMap和ConcurrentHashMap知识点总结

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

  3. java8中的HashMap

    简介: HashMap: 具有很快的访问速度,但遍历顺序却是不确定的. HashMap最多只允许一条记录的键为null,允许多条记录的值为null. HashMap非线程安全,即任一时刻可以有多个线程 ...

  4. java7,java8 中HashMap和ConcurrentHashMap简介

    一:Java7 中的HashMap 结构: HashMap 里面是一个数组,然后数组中每个元素是一个单向链表.链表中每个元素称为一个Entry 实例,Entry 包含四个属性:key, value, ...

  5. 【转】java8中谨慎使用实数作为HashMap的key!

    java8中谨慎使用实数作为HashMap的key! java8中一个hashCode()函数引发的血案java8中一个hashCode()函数引发的血案1.起因2.实数的hashCode()3.总结 ...

  6. java8中hashMap

    摘自:http://www.importnew.com/20386.html 简介 Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMa ...

  7. java8中map的meger方法的使用

    java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...

  8. Java8 中 ConcurrentHashMap工作原理的要点分析

    简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些 ...

  9. Jdk1.8中的HashMap实现原理

    HashMap概述 HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. HashM ...

随机推荐

  1. Spring中的FactoryBean

    从SessionFactory说起: 在使用SSH集成开发的时候,我们有时候会在applicationContext.xml中配置Hibernate的信息,以下是配置SessionFactory的一段 ...

  2. jfinal常见问题

    2014年的时候,学过一段时间的JFinal,当时主要是了解这个框架,研究了下源码,看懂了部分.今天,2015年2月7日,弄了一下午的JFinal,把未来要上线的一个官网项目,迁移到了JFinal.下 ...

  3. 使用Curl进行抓取远程内容时url中文编码问题

    PHP中对于URL进行编码,可以使用 urlencode() 或者 rawurlencode(),二者的区别是前者把空格编码为 '+',而后者把空格编码为 '%20',不过应该注意的是,在编码时应该只 ...

  4. 搜索:POJ2251&POJ1426&POJ3087&POJ2488

    图的遍历也称为搜索,就是从图中某个顶点出发,沿着一些边遍历图中所有的顶点,且每个顶点仅被访问一次,遍历可采取两种不同的方式:深度优先搜索(DFS)和广度优先搜索(BFS). 1.DFS算法思想` 从顶 ...

  5. 14.4.3.3 Making the Buffer Pool Scan Resistant

    14.4.3.3 Making the Buffer Pool Scan Resistant 让Buffer Pool 扫描 相比使用一个严格的LRU算法, InnoDB 使用一个技术来最小化数据的总 ...

  6. android端向服务器提交请求的几种方式

    1.GET方式 其实GET方式说白了,就是拼接字符串..最后拼成的字符串的格式是: path ?  username= ....& password= ...... public boolea ...

  7. 60s 经济学探奇

    理解经济学 什么是经济学.对于学习金融的同学,一定会给你搬出一大堆定义.例证.学派.说经济学是一门研究研究价值的生产.流通.分配.消费的规律的理论. 非常高大上的感觉,可是对于我这样没有什么金融学理论 ...

  8. extjs desktop startmenu (開始菜单)

    extjs desktop 的開始菜单 二级菜单,仅仅是简单演示实现原理,如 须要动态生成,自己改造就可以,下面基本方法原理: 首先 建立一个js文件 生成開始菜单数据:  function Get ...

  9. [IDEs]Eclipse自动格式化代码

    格式化代码快捷键:Ctrl + Shift + F 一般情况: 1).Ctrl + A 2).Ctrl + Shift + F ps: 格式化之后发现代码换行了,因为已经达到最大长度,可修改设置,增加 ...

  10. [C++]Hello C++

    最先进项目中需要用到C++做开发,所以开始学习C++,典型的眼高手低,刚开始觉得还算上手,之后越学越觉得复杂. 相比C#,C++确实需要开发者投入更多的精力去设计与维护. 以下是最近对C++开发的一些 ...