HashMap源码解读

/**

* Hash table based implementation of the <tt>Map</tt> interface. This

* implementation provides all of the optional map operations, and permits

* <tt>null</tt> values and the <tt>null</tt> key. (The <tt>HashMap</tt>

* class is roughly equivalent to <tt>Hashtable</tt>, 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.

 

HashMap允许空值在源码的注释中已经说明了

HashMap与HashTable唯一的不同就是HashMap不是线程安全的而且允许空


 

 

HashMap = 数组 + 单向链表

 

 

回顾一下数组和链表:

数组:ArrayList ---> Object [ ]

链表:LinkedList(双向链表)

 

 

想看懂HashMap的源码的存储方式,首先得知道HashMap存储的数据结构

 

内部类

/**

* Basic hash bin node, used for most entries. (See below for

* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)

*/

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;

}

}

 

 

/**

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

 

关键:Node数组    每个节点又可以纵向的挂节点

 

敲打

那么问题来了,数组的大小是多少呢?最大能有多少呢?什么时候需要扩容,怎么实现扩容?每个节点又能再挂多少个节点?

这些问题大概都是面试种经常爱问的。

 

 

默认容量大小

/**

* The default initial capacity - MUST be a power of two.

*/

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

 

默认容量大小为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;

 

最大容量为2的30次方

 

负载因子

/**

* The load factor used when none specified in constructor.

*/

static final float DEFAULT_LOAD_FACTOR = 0.75f;

 

如16 * 0.75 = 12

当数组中的元素增长到12时,就会扩容

 

如何扩容???    --------->  double扩容 (通过resize方法)    ------>为什么double扩容,后面再详细说明 

 

 

数组都有最大值了,那么链表有最大值吗

链表总不能一直往下挂把。。。惊讶

会有一个限制

 

/**

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

每个节点最多挂8

如果超过8,就要改变链表结构

链表   ------ >  平衡二叉树

 

 

平衡二叉树 红黑树

 

Node ------------>TreeNode

 

/**

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

 

如果小于6,平衡二叉树再变回为链表

TreeNode ------------>Node

 

那么问题又tm来了骂人

 

16个大小的数组

那么每塞一个Node的位置怎么确定? node内部类有个变量hash就是用来确定位置的

 

 

 

Object --- hashCode( ) ---> 得到32位整型数(二进制)

 

在这里顺便解读下hashCode( )  和  equals( )

在Object中

/**

* Returns a hash code value for the object. This method is

* supported for the benefit of hash tables such as those provided by

* {@link java.util.HashMap}.

* <p>

* The general contract of {@code hashCode} is:

* <ul>

* <li>Whenever it is invoked on the same object more than once during

* an execution of a Java application, the {@code hashCode} method

* must consistently return the same integer, provided no information

* used in {@code equals} comparisons on the object is modified.

* This integer need not remain consistent from one execution of an

* application to another execution of the same application.

* <li>If two objects are equal according to the {@code equals(Object)}

* method, then calling the {@code hashCode} method on each of

* the two objects must produce the same integer result.

* <li>It is <em>not</em> required that if two objects are unequal

* according to the {@link java.lang.Object#equals(java.lang.Object)}

* method, then calling the {@code hashCode} method on each of the

* two objects must produce distinct integer results. However, the

* programmer should be aware that producing distinct integer results

* for unequal objects may improve the performance of hash tables.

* </ul>

* <p>

* As much as is reasonably practical, the hashCode method defined by

* class {@code Object} does return distinct integers for distinct

* objects. (This is typically implemented by converting the internal

* address of the object into an integer, but this implementation

* technique is not required by the

* Java&trade; programming language.)

*

* @return a hash code value for this object.

* @see java.lang.Object#equals(java.lang.Object)

* @see java.lang.System#identityHashCode

*/

public native int hashCode();

 

equal返回true hashCode一样

equal返回false hashCode不一定不一样

 

根据key求hash的源码

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

 

(h = key.hashCode()) ^ (h >>> 16)
高16位和低16位取异或   把32位hashCode充分利用  使得到的hash值尽可能的不同

 

有了hash值 那么就可以通过hash值确定节点的位置

   

hash % 16

 

与运算效率高 hash & (16-1) 即hash & 15

所以在源码中用&运算替代%运算

 

回到前面为何会到容量阈值时要double扩容

初始是16

&运算时  hash & (16-1) ----->  hash & 15     ------>15的2进制是01111

 

 

15的二进制后面几位全是1才能保证与运算得到的值完全取决于hash值

double扩容也是这个原理

16*2=32             hash & ( 32-1 ) ------->  hash & 31

 

double扩容

 

 

扩容后还需要为其他节点分担

每次扩容,需要重新计算hash,重新打乱,目的是更好的利用节点

 

假定节点在原,数组中扩容后,原始节点的位置:

 

可能在两个位置,原位置或( 原下标+原容量)的位置

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值