HashMap 是 Java 开发过程中常用的工具类之一,也是面试过程中常问的内容,此篇文件通过作者自己的理解和网上众多资料对其进行一个解析。作者本地的 JDK 版本为 64 位的 1.8.0_171。参考资料推荐以下两篇文章:

数据结构



结合上图及源码可以看出,HashMap 底层数据结构为 Node 类型数组,Node 类型为 HashMap 的内部类,数据结构为链表

  1. /**
  2. * The table, initialized on first use, and resized as
  3. * necessary. When allocated, length is always a power of two.
  4. * (We also tolerate length zero in some operations to allow
  5. * bootstrapping mechanics that are currently not needed.)
  6. */
  7. transient Node<K,V>[] table;
  8. /**
  9. * Basic hash bin node, used for most entries. (See below for
  10. * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
  11. */
  12. static class Node<K,V> implements Map.Entry<K,V> {
  13. final int hash;
  14. final K key;
  15. V value;
  16. Node<K,V> next;
  17. Node(int hash, K key, V value, Node<K,V> next) {
  18. this.hash = hash;
  19. this.key = key;
  20. this.value = value;
  21. this.next = next;
  22. }
  23. public final K getKey() { return key; }
  24. public final V getValue() { return value; }
  25. public final String toString() { return key + "=" + value; }
  26. public final int hashCode() {
  27. return Objects.hashCode(key) ^ Objects.hashCode(value);
  28. }
  29. public final V setValue(V newValue) {
  30. V oldValue = value;
  31. value = newValue;
  32. return oldValue;
  33. }
  34. public final boolean equals(Object o) {
  35. if (o == this)
  36. return true;
  37. if (o instanceof Map.Entry) {
  38. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  39. if (Objects.equals(key, e.getKey()) &&
  40. Objects.equals(value, e.getValue()))
  41. return true;
  42. }
  43. return false;
  44. }
  45. }

初始大小

HashMap 默认的初始大小为 16,如有特殊情况下需要自定义初始化大小时可调用 HashMap(int initialCapacity) 方法进行自定义。

  1. /**
  2. * The default initial capacity - MUST be a power of two.
  3. */
  4. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  5. /**
  6. * Constructs an empty <tt>HashMap</tt> with the specified initial
  7. * capacity and the default load factor (0.75).
  8. *
  9. * @param initialCapacity the initial capacity.
  10. * @throws IllegalArgumentException if the initial capacity is negative.
  11. */
  12. public HashMap(int initialCapacity) {
  13. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  14. }

负载因子

负载因子默认为 0.75,当 HashMap 当前已使用容量大于当前大小 * 负载因子时,自动扩容一倍空间,如有特殊情况下需要自定义初始化大小时可调用 以下方法进行自定义。

  1. /**
  2. * The load factor used when none specified in constructor.
  3. */
  4. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  5. /**
  6. * Constructs an empty <tt>HashMap</tt> with the specified initial
  7. * capacity and load factor.
  8. *
  9. * @param initialCapacity the initial capacity
  10. * @param loadFactor the load factor
  11. * @throws IllegalArgumentException if the initial capacity is negative
  12. * or the load factor is nonpositive
  13. */
  14. public HashMap(int initialCapacity, float loadFactor) {
  15. if (initialCapacity < 0)
  16. throw new IllegalArgumentException("Illegal initial capacity: " +
  17. initialCapacity);
  18. if (initialCapacity > MAXIMUM_CAPACITY)
  19. initialCapacity = MAXIMUM_CAPACITY;
  20. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  21. throw new IllegalArgumentException("Illegal load factor: " +
  22. loadFactor);
  23. this.loadFactor = loadFactor;
  24. this.threshold = tableSizeFor(initialCapacity);
  25. }

树型阀值

树型阀值这个名字是作者根据字面意思自己翻译的,大家看看就好了,对应参数为TREEIFY_THRESHOLD,之前提到过 HashMap 的结构为 Node 型数组,而 Node 的数据结构为链表,树型阀值就是当链表长度超过这个值时,将 Node 的数据结构修改为红黑树,以便优化查找时间,默认值为8

  1. /**
  2. * The bin count threshold for using a tree rather than list for a
  3. * bin. Bins are converted to trees when adding an element to a
  4. * bin with at least this many nodes. The value must be greater
  5. * than 2 and should be at least 8 to mesh with assumptions in
  6. * tree removal about conversion back to plain bins upon
  7. * shrinkage.
  8. */
  9. static final int TREEIFY_THRESHOLD = 8;

初始化

HashMap 提供以下四种构造方法进行初始化,前三种主要区别在于设置以上介绍的几个参数,第四种方法为通过其他 Map 实现创建 HashMap。

  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the specified initial
  3. * capacity and load factor.
  4. *
  5. * @param initialCapacity the initial capacity
  6. * @param loadFactor the load factor
  7. * @throws IllegalArgumentException if the initial capacity is negative
  8. * or the load factor is nonpositive
  9. */
  10. public HashMap(int initialCapacity, float loadFactor) {
  11. if (initialCapacity < 0)
  12. throw new IllegalArgumentException("Illegal initial capacity: " +
  13. initialCapacity);
  14. if (initialCapacity > MAXIMUM_CAPACITY)
  15. initialCapacity = MAXIMUM_CAPACITY;
  16. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  17. throw new IllegalArgumentException("Illegal load factor: " +
  18. loadFactor);
  19. this.loadFactor = loadFactor;
  20. this.threshold = tableSizeFor(initialCapacity);
  21. }
  22. /**
  23. * Constructs an empty <tt>HashMap</tt> with the specified initial
  24. * capacity and the default load factor (0.75).
  25. *
  26. * @param initialCapacity the initial capacity.
  27. * @throws IllegalArgumentException if the initial capacity is negative.
  28. */
  29. public HashMap(int initialCapacity) {
  30. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  31. }
  32. /**
  33. * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  34. * (16) and the default load factor (0.75).
  35. */
  36. public HashMap() {
  37. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  38. }
  39. /**
  40. * Constructs a new <tt>HashMap</tt> with the same mappings as the
  41. * specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
  42. * default load factor (0.75) and an initial capacity sufficient to
  43. * hold the mappings in the specified <tt>Map</tt>.
  44. *
  45. * @param m the map whose mappings are to be placed in this map
  46. * @throws NullPointerException if the specified map is null
  47. */
  48. public HashMap(Map<? extends K, ? extends V> m) {
  49. this.loadFactor = DEFAULT_LOAD_FACTOR;
  50. putMapEntries(m, false);
  51. }

设置 HashMap 的值

put 方法是 HashMap 中使用率非常高的 API 之一,其源码实现如下,通过源码我们可以发现其原理主要分为以下两步:

  • 对 key 进行 hash 运算,然后再与当前 map 最后一个下标进行与运算确定其在数组中的位置,正是因为这个算法,我们可以得知 HashMap 中元素是无序的。
  • 确定其下标以后,如果当前位置为空则直接赋值,如果不为空则放到下一个节点,如果当前为链表且添加元素后的长度达到树型阀值,则将链表转换为红黑树
  1. /**
  2. * Associates the specified value with the specified key in this map.
  3. * If the map previously contained a mapping for the key, the old
  4. * value is replaced.
  5. *
  6. * @param key key with which the specified value is to be associated
  7. * @param value value to be associated with the specified key
  8. * @return the previous value associated with <tt>key</tt>, or
  9. * <tt>null</tt> if there was no mapping for <tt>key</tt>.
  10. * (A <tt>null</tt> return can also indicate that the map
  11. * previously associated <tt>null</tt> with <tt>key</tt>.)
  12. */
  13. public V put(K key, V value) {
  14. return putVal(hash(key), key, value, false, true);
  15. }
  16. /**
  17. * Implements Map.put and related methods
  18. *
  19. * @param hash hash for key
  20. * @param key the key
  21. * @param value the value to put
  22. * @param onlyIfAbsent if true, don't change existing value
  23. * @param evict if false, the table is in creation mode.
  24. * @return previous value, or null if none
  25. */
  26. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  27. boolean evict) {
  28. Node<K,V>[] tab; Node<K,V> p; int n, i;
  29. if ((tab = table) == null || (n = tab.length) == 0)
  30. n = (tab = resize()).length;
  31. if ((p = tab[i = (n - 1) & hash]) == null)
  32. tab[i] = newNode(hash, key, value, null);
  33. else {
  34. Node<K,V> e; K k;
  35. if (p.hash == hash &&
  36. ((k = p.key) == key || (key != null && key.equals(k))))
  37. e = p;
  38. else if (p instanceof TreeNode)
  39. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  40. else {
  41. for (int binCount = 0; ; ++binCount) {
  42. if ((e = p.next) == null) {
  43. p.next = newNode(hash, key, value, null);
  44. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  45. treeifyBin(tab, hash);
  46. break;
  47. }
  48. if (e.hash == hash &&
  49. ((k = e.key) == key || (key != null && key.equals(k))))
  50. break;
  51. p = e;
  52. }
  53. }
  54. if (e != null) { // existing mapping for key
  55. V oldValue = e.value;
  56. if (!onlyIfAbsent || oldValue == null)
  57. e.value = value;
  58. afterNodeAccess(e);
  59. return oldValue;
  60. }
  61. }
  62. ++modCount;
  63. if (++size > threshold)
  64. resize();
  65. afterNodeInsertion(evict);
  66. return null;
  67. }

获取HashMap中的值

get 方法同样是 HashMap 中常用的 API 之一,参照其源码,其原理与 put 方法正好相反,分为以下两个部分:

  • 根据 key 的 hash 运算值获取数组中对应下标的内容
  • 循环链表或红黑树,然后匹配 value 值直至获得对应的值或返回 null
  1. /**
  2. * Returns the value to which the specified key is mapped,
  3. * or {@code null} if this map contains no mapping for the key.
  4. *
  5. * <p>More formally, if this map contains a mapping from a key
  6. * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
  7. * key.equals(k))}, then this method returns {@code v}; otherwise
  8. * it returns {@code null}. (There can be at most one such mapping.)
  9. *
  10. * <p>A return value of {@code null} does not <i>necessarily</i>
  11. * indicate that the map contains no mapping for the key; it's also
  12. * possible that the map explicitly maps the key to {@code null}.
  13. * The {@link #containsKey containsKey} operation may be used to
  14. * distinguish these two cases.
  15. *
  16. * @see #put(Object, Object)
  17. */
  18. public V get(Object key) {
  19. Node<K,V> e;
  20. return (e = getNode(hash(key), key)) == null ? null : e.value;
  21. }
  22. /**
  23. * Implements Map.get and related methods
  24. *
  25. * @param hash hash for key
  26. * @param key the key
  27. * @return the node, or null if none
  28. */
  29. final Node<K,V> getNode(int hash, Object key) {
  30. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  31. if ((tab = table) != null && (n = tab.length) > 0 &&
  32. (first = tab[(n - 1) & hash]) != null) {
  33. if (first.hash == hash && // always check first node
  34. ((k = first.key) == key || (key != null && key.equals(k))))
  35. return first;
  36. if ((e = first.next) != null) {
  37. if (first instanceof TreeNode)
  38. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  39. do {
  40. if (e.hash == hash &&
  41. ((k = e.key) == key || (key != null && key.equals(k))))
  42. return e;
  43. } while ((e = e.next) != null);
  44. }
  45. }
  46. return null;
  47. }
  48. /**
  49. * Calls find for root node.
  50. */
  51. final TreeNode<K,V> getTreeNode(int h, Object k) {
  52. return ((parent != null) ? root() : this).find(h, k, null);
  53. }

详解 Java 8 HashMap 实现原理的更多相关文章

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

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

  2. 详解Java GC的工作原理

    JVM学习笔记之JVM内存管理和JVM垃圾回收的概念,JVM内存结构由堆.栈.本地方法栈.方法区等部分组成,另外JVM分别对新生代和旧生代采用不同的垃圾回收机制. 首先来看一下JVM内存结构,它是由堆 ...

  3. 「跬步千里」详解 Java 内存模型与原子性、可见性、有序性

    文题 "跬步千里" 主要是为了凸显这篇文章的基础性与重要性(狗头),并发编程这块的知识也确实主要围绕着 JMM 和三大性质来展开. 全文脉络如下: 1)为什么要学习并发编程? 2) ...

  4. 实例详解 Java 死锁与破解死锁

    锁和被保护资源之间的关系 我们把一段需要互斥执行的代码称为临界区.线程在进入临界区之前,首先尝试加锁 lock(),如果成功,则进入临界区,此时我们称这个线程持有锁:否则呢就等待,直到持有锁的线程解锁 ...

  5. Protocol Buffer技术详解(Java实例)

    Protocol Buffer技术详解(Java实例) 该篇Blog和上一篇(C++实例)基本相同,只是面向于我们团队中的Java工程师,毕竟我们项目的前端部分是基于Android开发的,而且我们研发 ...

  6. 详解Java中的clone方法

    详解Java中的clone方法 参考:http://blog.csdn.net/zhangjg_blog/article/details/18369201/ 所谓的复制对象,首先要分配一个和源对象同样 ...

  7. java基础(十五)----- Java 最全异常详解 ——Java高级开发必须懂的

    本文将详解java中的异常和异常处理机制 异常简介 什么是异常? 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常. Java异常的分类和类结构图 1.Java中的所 ...

  8. 异常处理器详解 Java多线程异常处理机制 多线程中篇(四)

    在Thread中有异常处理器相关的方法 在ThreadGroup中也有相关的异常处理方法 示例 未检查异常 对于未检查异常,将会直接宕掉,主线程则继续运行,程序会继续运行 在主线程中能不能捕获呢? 我 ...

  9. 第三节:带你详解Java的操作符,控制流程以及数组

    前言 大家好,给大家带来带你详解Java的操作符,控制流程以及数组的概述,希望你们喜欢 操作符 算数操作符 一般的 +,-,*,/,还有两个自增 自减 ,以及一个取模 % 操作符. 这里的操作算法,一 ...

随机推荐

  1. kettle的job中运行每行

     job中运行每行 有时,我们须要job或转换执行多次.且每次传入的參数都不同.假如你正在做数据迁移的工作,须要导入每天的旧数据,则须要一个job依据指定的日期导入数据,该日期被指定作为參数.假设 ...

  2. 一起学Python: 多线程-共享全局变量问题

    多线程-共享全局变量问题 多线程开发可能遇到的问题 假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20. 但是 ...

  3. 检索06 - Oracle MySql SqlSever之间的区别和优缺点

    三者之间区别 历史 1 Oracle:中文译作甲骨文,这是一家传奇的公司,有一个传奇的大老板Larry Ellision. Ellision 32岁还一事无成,读了三个大学,没得到一个学位文凭,换了十 ...

  4. Method for sub-pixel texture mapping and filtering

    BACKGROUND OF THE INVENTION 1. Field of the Invention The present invention relates to a method for ...

  5. Gradle构建脚本基础

    Gradle构建脚本,内部是基于 Groovy 的 DSL(领域特点语言),而Maven是基于XML的,Groovy相比XML更加简洁.灵活和强大. Groovy 因为给 Java 开发人员提供了最大 ...

  6. windows 系统本地做mysql 主从同步,最后面解决主从同步库名不一致,表结构一致

    原文:windows 系统本地做mysql 主从同步,最后面解决主从同步库名不一致,表结构一致 mysql主从同步的好处以及原理       之前看到很多新闻说某某的服务器奔溃,磁盘碎了,导致数据丢失 ...

  7. windows 下 gcc/g++ 的安装(有图,一步一步)

    下载 mingw 首先打开 www.mingw.org .(注意版本,建议64bit) www.mingw.org 直接点击右上方的 Download Installer 即可下载. 点击 Downl ...

  8. 多线程中的lock,Monitor.Wait和Monitor.Pulse

    我们知道lock实际上一个语法糖糖,C#编译器实际上把他展开为Monitor.Enter和Monitor.Exit,即: lock(lockObj) { //... } ////相当于(.Net4以前 ...

  9. hdu 2037 这个夏天不AC

    这个夏天不AC Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Su ...

  10. 买不起360随声wifi怎么办?这些都不是问题

    只需轻松一步,点击开启共享 软件下载地址:http://download.csdn.net/detail/lxq_xsyu/6384265 如果身边没有数据线怎么办?? 使用方法: 1.用手机连接Wi ...