烧点脑子使劲看--HashMap源码分析(上)

简介: 概述HashMap最早出现在JDK1.2中,是基于Map接口的实现,储存的内容是键值对(key-value),HashMap中的键和值都可以为null。HashMap的实现并不是同步的,也就是说它不是线程安全的。

一、概述


HashMap最早出现在JDK1.2中,是基于Map接口的实现,储存的内容是键值对(key-value),HashMap中的键和值都可以为null。HashMap的实现并不是同步的,也就是说它不是线程安全的。


二、数据结构


在JDK1.8中,HashMap的数据结构为数组+链表+红黑树,红黑树是HashMap在JDK1.8新引入的数据结构,主要是为了解决链表过长导致查询效率变成O(n)的问题。

当插入一个键值对时,HashMap会根据key计算出hash,再根据hash确定在数组中的位置,如果发生hash碰撞,将使用链表的形式储存,当链表过长时,将链表转为红黑树。


三、属性


默认容量

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

最大容量

/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

默认负载因子

/**
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;


将链表转为红黑树的阈值

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


将红黑树转为链表的阈值

/**
 * The bin count threshold for untreeifying a (split) bin during a
 * resize operation. Should be less than TREEIFY_THRESHOLD, and at
 * most 6 to mesh with shrinkage detection under removal.
 */
static final int UNTREEIFY_THRESHOLD = 6;


将链表转为红黑树的最小容量

/**
 * The smallest table capacity for which bins may be treeified.
 * (Otherwise the table is resized if too many nodes in a bin.)
 * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;


储存元素的数组

/**
 * The table, initialized on first use, and resized as
 * necessary. When allocated, length is always a power of two.
 * (We also tolerate length zero in some operations to allow
 * bootstrapping mechanics that are currently not needed.)
 */
transient Node<K,V>[] table;


将数据转化为Set的形式储存,主要用于迭代

/**
 * Holds cached entrySet(). Note that AbstractMap fields are used
 * for keySet() and values().
 */
transient Set<Map.Entry<K,V>> entrySet;


数组中元素的个数

/**
 * The number of key-value mappings contained in this map.
 */
transient int size;


修改次数

/**
 * The number of times this HashMap has been structurally modified
 * Structural modifications are those that change the number of mappings in
 * the HashMap or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the HashMap fail-fast.  (See ConcurrentModificationException).
 */
transient int modCount;


扩容阈值

/**
 * The next size value at which to resize (capacity * load factor).
 *
 * @serial
 */
// (The javadoc description is true upon serialization.
// Additionally, if the table array has not been allocated, this
// field holds the initial array capacity, or zero signifying
// DEFAULT_INITIAL_CAPACITY.)
int threshold;


负载因子

/**
 * The load factor for the hash table.
 *
 * @serial
 */
final float loadFactor;


5.3 get

get方法首先调用hash方法,计算出key的哈希值,在通过getNode方法得到对应的元素,如果不等于null,返回元素的value,否则返回null。

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}


getNode方法实现:

final Node<K,V> getNode(int hash, Object key) {
  Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  // 数组已经初始化,并且传入的hash值对应的数组下标元素不为null
  if ((tab = table) != null && (n = tab.length) > 0 &&
    (first = tab[(n - 1) & hash]) != null) {
    // key等于头节点的key,返回头节点
    if (first.hash == hash &&
      ((k = first.key) == key || (key != null && key.equals(k))))
      return first;
    // key不等于头节点的key
    if ((e = first.next) != null) {
      // 红黑树情况
      if (first instanceof TreeNode)
        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
      // 链表情况,遍历链表,直到找到相同的key或者遍历到结尾
      do {
        if (e.hash == hash &&
          ((k = e.key) == key || (key != null && key.equals(k))))
          return e;
      } while ((e = e.next) != null);
    }
  }
  return null;
}


5.4 remove

remove方法同样先调用hash方法计算出key的哈希值,再调用removeNode方法移除元素。

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}


removeNode方法:

final Node<K,V> removeNode(int hash, Object key, Object value,
               boolean matchValue, boolean movable) {
  Node<K,V>[] tab; Node<K,V> p; int n, index;
  // 数组已经初始化,并且传入的hash值对应的数组下标元素不为null
  if ((tab = table) != null && (n = tab.length) > 0 &&
    (p = tab[index = (n - 1) & hash]) != null) {
    Node<K,V> node = null, e; K k; V v;
    // 传入的key等于头节点的key,待删除的节点为头节点
    if (p.hash == hash &&
      ((k = p.key) == key || (key != null && key.equals(k))))
      node = p;
    else if ((e = p.next) != null) {
      // 红黑树情况,调用TreeNode.getTreeNode方法获得要删除的节点
      if (p instanceof TreeNode)
        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
      // 链表情况,遍历得到要删除的节点
      else {
        do {
          if (e.hash == hash &&
            ((k = e.key) == key ||
             (key != null && key.equals(k)))) {
            node = e;
            break;
          }
          p = e;
        } while ((e = e.next) != null);
      }
    }
    // 如果node不等于null,说明之前匹配到了要删除的节点
    // 如果matchValue等于false,则不需要匹配该节点的value,否则需要node的value和传入的value相等才能删除
    if (node != null && (!matchValue || (v = node.value) == value ||
               (value != null && value.equals(v)))) {
      // 如果该节点是红黑树节点,调用TreeNode.removeTreeNode方法移除节点
      if (node instanceof TreeNode)
        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
      // 如果该节点是头节点,只需要把数组对应的下标指向下一个节点
      else if (node == p)
        tab[index] = node.next;
      // 如果不是头节点,此时的p为前一个节点,只需要将前一个节点的next指向当前节点的next
      else
        p.next = node.next;
      // 修改次数+1
      ++modCount;
      // 元素个数-1
      --size;
      afterNodeRemoval(node);
      return node;
    }
  }
  return null;
}



目录
相关文章
|
7月前
|
Java
【JDK 源码分析】HashMap 操作方法
【1月更文挑战第27天】【JDK 源码分析】HashMap Put 元素 初始化
|
2月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
35 1
|
4月前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
6月前
|
存储 Java 测试技术
滚雪球学Java(66):Java之HashMap详解:深入剖析其底层实现与源码分析
【6月更文挑战第20天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
49 3
滚雪球学Java(66):Java之HashMap详解:深入剖析其底层实现与源码分析
|
6月前
|
存储 算法 安全
JDK源码分析-HashMap
JDK源码分析-HashMap
|
7月前
|
存储 算法
HashMap源码分析
HashMap源码分析
|
7月前
|
安全 Java
【JDK 源码分析】HashMap 线程安全问题分析
【1月更文挑战第27天】【JDK 源码分析】HashMap 线程安全问题分析
|
7月前
|
存储 Java
【JDK 源码分析】HashMap 底层结构
【1月更文挑战第27天】【JDK 源码分析】HashMap 底层结构
|
7月前
|
存储 算法 Java
HashMap的源码分析(基于JDK1.8)
HashMap的源码分析(基于JDK1.8) Java中的HashMap是一种常用的数据结构,它是基于哈希表的数据结构,可以用来存储键值对。在HashMap中,每个键值对被称作一个Entry,每个Entry包含一个键和一个值。HashMap的实现基于数组和链表,数组用于存储Entry,链表用于解决哈希冲突。
|
存储 Java
[java进阶]——HashMap的底层实现原理和源码分析,另附几个高频面试题
[java进阶]——HashMap的底层实现原理和源码分析,另附几个高频面试题
181 0
下一篇
DataWorks