1. HashMap

    Hash table based implementation of the Map interface. This

    implementation provides all of the optional map operations, and permits

    null values and the null key. (The HashMap class is roughly equivalent

    to Hashtable, except that it is unsynchronized and permits nulls.)

    This class makes no guarantees as to the order of the map; in particular,

    it does not guarantee that the order will remain constant over time.

    官方文档描述信息:基于Map接口实现,键值都允许null,非线程同步的,不按插入顺序排,也不保证不随时间变化。

    HashMap底层的数据结构实现是数组加链表,数组的每一项都是链。

  2. 构造函数

    HashMap提供了四个构造函数:

    • HashMap(int initialCapacity, float loadFactor):构造一个带有指定容量和加载因子的空的HashMap。

    • HashMap(int initialCapacity):构造一个指定容量和默认加载因子为0.75的空的HashMap

    • HashMap():构造一个默认容量为16和默认加载因子为0.75的空的HashMap。

    • HashMap(Map<? extends K, ? extends V> m):构造一个匹配所有map中所有的元素并且加载因子是0.75的空HashMap。

    初始容量和加载因子是影响HashMap性能的重要参数。

    • 初始容量:创建哈希表时的容量(bucket)

    • 加载因子:哈希表在其容量自动增加之前可以达到多满的一个尺度。

  3. put()的实现

    put()大致的思路:

    1. 对key的hashCode()做hash,然后再计算index
    2. 如果没碰到直接放到bucket里
    3. 如果碰撞了,以链表的形式存在buckets后
    4. 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链接表转换成红黑树
    5. 如果节点已经存在就替换old value(保证key的唯一性)
    6. 如果bucket满了(超过加载因子 * 当前容量),就要resize
    public V put(K key, V value) {
// 对key的hashCode()做hash()
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;
// 计算index并做特殊处理
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 如果hash和key都相同
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;
}
  1. get()的实现

    get()的大致思路:

    1. bucket里的第一个节点,直接命中;
    2. 如果有冲突,则通过key.equals(k)去查找对应的entry

      若为树,则在树中通过key.equals(k)查找

      若为链表,则在链表中通过key.equals(k)查找
    public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
} final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// table不为空才进行以下操作,table为null的话直接返回null
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
// 直接命中
if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
// 在树中命中
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 在链表中命中
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
  1. hash()的实现
    static final int hash(Object key) {
int h;
// 使用key的hashCode进行hash计算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这个函数的作用是:高16bit不变,低16bit和高16bit做了一个异或。
在计算下标的时候是这样实现的:
    tab[i = (n - 1) & hash] // 使用&操作,而非%操作
  1. resize()的实现

    当put时,当超过限制的时候会resize,然而又因为我们使用的2次幂的扩展(指长度扩展为原来的2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。

  2. 面试题

    1. HashMap有什么特点?

      基于Map接口实现,存储键值对时,他可以接受null的键值,是非同步的,HashMap存储着Entry对象

    2. 你知道HashMap的工作原理么?

      通过hash的方法,通过put和get存储获取对象。存储时,我们将k/v传给put方法时,通过获取k的hashCode并计算hash值从而获取到bucket的位置,进一步存储,HashMap会根据当前bucket的占用情况自动扩容(当超出 加载因子 * 当前容量 时扩容到当前容量的两倍)。获取对象时,我们将k传给get方法,通过获取k的hashCode并计算hash值获取到在bucket中的位置,并进一步调用获取equals()获取键值对。如果发生碰撞时,HashMap通过链表将产生碰撞冲突的元素组织起来,在Java8中,当一个bucket的存储容量超过某个限制(默认是8)时就会用红黑树来代替链表,从而提高速度。

    3. 你知道get和put的原理么?equals()和hashCode都有什么用?

      通过对key的hashCode()进行hashing,通过(n - 1 & hash)计算下标,当发生碰撞时,则利用key.equals()方法去链表或者树中查找对应的键值对。

    4. 你知道hash的实现么?为什么要这样的实现?

           (h = key.hashCode()) ^ (h >>> 16)

      这么做可以在bucket的n比较小的时候,也能保证考虑到高地bit都参与到hash的计算中,同时不会有太大的开销

    5. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

      当超过了负载因子,会重新resize一个原来长度两倍的HashMap,并重新调用hash方法

参考文章:

  1. http://yikun.github.io/2015/04/01/Java-HashMap工作原理及实现/#7-_总结
  2. http://cmsblogs.com/?p=176

【Java基础】HashMap工作原理的更多相关文章

  1. Java HashMap工作原理及实现

    Java HashMap工作原理及实现 2016/03/20 | 分类: 基础技术 | 0 条评论 | 标签: HASHMAP 分享到:3 原文出处: Yikun 1. 概述 从本文你可以学习到: 什 ...

  2. Java基础-hashMap原理剖析

    Java基础-hashMap原理剖析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.什么是哈希(Hash) 答:Hash就是散列,即把对象打散.举个例子,有100000条数 ...

  3. HTTPS那些事 用java实现HTTPS工作原理

    HTTPS那些事 用java实现HTTPS工作原理 博客分类: java历险   今天被问到关于https原理的问题,结果由于知识掌握不牢靠,停留于表面,很多细节都无法回答清楚,于是决定把https的 ...

  4. 详解Java GC的工作原理+Minor GC、FullGC

    详解Java GC的工作原理+Minor GC.FullGC 引用地址:http://www.blogjava.net/ldwblog/archive/2013/07/24/401919.html J ...

  5. java gc的工作原理、如何优化GC的性能、如何和GC进行有效的交互

    java gc的工作原理.如何优化GC的性能.如何和GC进行有效的交互 一个优秀的Java 程序员必须了解GC 的工作原理.如何优化GC的性能.如何和GC进行有效的交互,因为有一些应用程序对性能要求较 ...

  6. Java Web程序工作原理

    Web开发的最重要的基本功能是HTTP:Java Web开发的最重要的基本功是Servlet Specification.HTTP和Servlet Specitication对于Web Server和 ...

  7. [翻译]Java HashMap工作原理

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...

  8. 【转】Java HashMap工作原理(好文章)

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...

  9. Java HashMap工作原理及实现[转]

    原文:http://yikun.github.io/2015/04/01/Java-HashMap%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E5%8F%8A%E5%AE ...

随机推荐

  1. db2 将原表列notnull属性修改为null属性的方法 (查看主键约束,唯一约束去syscat.tabconst)

    好久没机会写点东西了,今天把自己遇到的一个小问题跟大家分享一下如何修改db2数据库表中列的属性--将列的非空属性改为允许空的属性,修改数据表的某一列属性其实很简单但是里面有需要细节需要dba注意,毕竟 ...

  2. EverNote剪藏插件安装问题

    安装EverNote印象笔记的剪藏插件时出现插件不能使用的问题,可以采用如下的方法(可以参考知乎的解决办法:https://www.zhihu.com/question/29875051) (下载地址 ...

  3. Mybatis学习(8)逆向工程

    什么是逆向工程: mybaits需要程序员自己编写sql语句,mybatis官方提供逆向工程 可以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xml.po.. ...

  4. win7(windows 7)系统下安装SQL2005(SQL Server 2005)图文教程——转载

    操作系统:Microsoft Windows 7 旗舰版(32位) 数据库版本:SQL Server 2005 简体中文开发板 数据库下载链接:http://pan.baidu.com/share/l ...

  5. systemd-journal[825]: Forwarding to syslog missed 99 messages

    In this particular case we have a DNS server running Bind which is logging all DNS queries. 7124 mes ...

  6. --@angularJS--angular与BootStrap3的应用

    angularJS与BootStrap3是最佳搭档. 1.Form.html: <!doctype html><html ng-app="UserInfoModule&qu ...

  7. c#中Partial关键字的作用

    1. 什么是局部类型? C# 2.0 引入了局部类型的概念.局部类型允许我们将一个类.结构或接口分成几个部分,分别实现在几个不同的.cs文件中. 局部类型适用于以下情况: (1) 类型特别大,不宜放在 ...

  8. easyui message show中msg嵌入一个按钮如何绑定事件

    http://www.oschina.net/question/945028_171927

  9. 无法删除MySql数据库,报错1010 error dropping

    环境:MySQL.Navicat 8 操作:Drop database testDB 报错:1010 error dropping database 解决方法:          ps -ef | g ...

  10. [转载]【虚拟化系列】VMware vSphere 5.1 网络管理

    转载自:http://mabofeng.blog.51cto.com/2661587/1020375 网络是VMware vSphere 5.1的基础,所有虚拟机都需要网络来进行通信.如果将所有的虚拟 ...