java基础解析系列(三)---HashMap

java基础解析系列

基本概念

  • 节点: Node<Key,Value>,存放key和value
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
  • 键值对数组:Node<K,V>[] table
  • 加载因子
  • 容量 :Node数组的长度
  • 大小:hashmap存放的Node的数目
  • 阈值:容量*加载因子

工作原理

  • 创建一个长度为2的次幂的node数组
  • put的时候,计算key的hash值,将hash值与长度-1进行与运算
  • 如果数组该下标的位置为空,直接存放,如果不为空,判断节点是否为树节点,如果是的话按红黑树的方式存入,否则按照链表的形式存入
  • 当hashmap的节点数目大于阈值的时候,将会重新构造hashmap,而这种操作是费时的操作,所以建议初始化一个合适的容量

  • 默认容量,2的四次方

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

  • 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • node 数组
 transient Node<K,V>[] table;

  • 键值对数目,不是table的长度
    /**
* The number of key-value mappings contained in this map.
*/ transient int size;
  • 阈值
    /**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
//阈值
int threshold;
  • 加载因子

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

构造方法

  • 传入初始容量和加载因子
 public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
  • 传入初始容量,使用默认的加载因子

public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  • 无参数,默认容量和加载因子
    /**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

  • 容量必须是2的n次方,当你传入的参数不符合条件,会有方法找到一个大于这个参数的最小的2的n次方数(比如大于6的最小2的n次幂是8),

put方法

public V put(K key, V value) {
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;
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;
}
  • 直接用伪代码表示


put()
{
index=[hash(key)&(captity-1)]----下标的最大值为captity-1,进行与运算后最终的结果小于等于最大下标
if(table[index])==null)
直接添加node
else
{
if(p是treenode)
{
直接将节点添加到红黑树
}
else
{
如果不是红黑树是链表
if(p的键值==key)
覆盖value
else
{
遍历链表:
{
if(有对应的key)
{
覆盖value
break;
}
}
遍历完成后没有发现对应的key
{
添加到链表
if(链表长度>8)
{
将链表转化为红黑树
}
}
}
}
if(大小大于阈值)
{
容量加倍,重新构造
} }
}

get方法

 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;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果链表的第一个节点是的键和要查找的键相等,那么返回该node
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;
}

为什么长度设置为2的n次方

if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
  • 存放node到table数组的时候,他的下标是通过(n-1)&hash计算出来的(数组长度-1 和 key的hash的值相与,最后结果小于等于长度-1),n为table的长度。
  • 当长度为2的n次幂的时候,(n-1)&hash==hash%n,而前者是位运算,速度会快很多

负载因子

  • 负载因子较大,说明阈值较大,也就意味着可能发生更多的冲突
  • 负载因子较小,说明阈值较小,也就意味着可能会更少的冲突
  • 发生冲突的时候,会降低hashmap的查找速度,所以当要求更少的内存的时候可以增加负载因子,当要求更高的查找速度的时候,可以减少负载因子。
  • 默认的参数是平衡的选择,所以不建议修改

我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

作者:jiajun 出处: http://www.cnblogs.com/-new/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

java基础解析系列(三)---HashMap的更多相关文章

  1. java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别

    java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...

  2. java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现

    java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...

  3. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

  4. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  5. java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理

    fail-fast机制及CopyOnWriteArrayList的原理 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列( ...

  6. java基础解析系列(九)---String不可变性分析

    java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...

  7. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  8. java基础解析系列(十一)---equals、==和hashcode方法

    java基础解析系列(十一)---equals.==和hashcode方法 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系 ...

  9. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

随机推荐

  1. 解决华为交换机S5700无法解除ip/Mac绑定的问题

    今天同事离职,需要解除他的个人笔记本Mac与ip的绑定 首先进入系统用户视图,然后进入vlanif4,解除151绑定 system-view interface vlanif 4 undo dhcp ...

  2. css实现梯形

    使用伪元素before和after分别在矩形元素前后加三角形或者直接设置border 使用3d旋转矩形,使之看起来像矩形 <html> <head> <meta char ...

  3. September 07th 2017 Week 36th Thursday

    With the most true of yourself, can you meet the most suitable one. 用最真实的自己,才能遇见最合适的那个人. You are alw ...

  4. java String,StringBuilder和StringBuffer

    String:1.java语言中的字符串值属于String类,虽然有其它方法表示字符串(如字符数组),但java一般使用Sting类作为字符串的标准格式,java编译器把字符串值作为String对象. ...

  5. 【收集】Python 微优化

    1. 第二种方式可以节省寻找result的append属性的时间, 但会降低代码可读性和可维护性 # The way we're used to seeing it: result.append(&q ...

  6. AOP-Pointcut-笔记

    一.Pointcut 这是切点的抽象.一个切点由一个的类过滤器和一个方法匹配器组成. 将整个代码贴上来 /** * Core Spring pointcut abstraction. * * < ...

  7. 在Windows10中更改”WIN+E“快捷键打开目标

    1> 复制下面代码到记事本保存为launch.vbs 2> 然后打开Regedit.exe并创建以下注册表分支 HKCU:\Software\Classes\CLSID\{52205fd8 ...

  8. 常用的sql语法_Row_Number

    可用来分页,也可以用来egg:获取同类型的最新的信息 ROW_NUMBER() 说明:返回结果集分区内行的序列号,每个分区的第一行从1开始.语法:ROW_NUMBER () OVER  ([ < ...

  9. Burpsuite-Intruder-xssValidator(XSS检测)基础学习

    这次总结的是使用Burp+PhantomJS进行xss测试. 首先,当然是xss测试的环境配置了. 1. PhantomJS安装及Path配置:自己找资料吧. phantomjs -v验证是否成功安装 ...

  10. 20165302 敏捷开发与XP实践作业

    20165302 敏捷开发与XP实践实验报告 一.提交点一 1.实验要求 参考 http://www.cnblogs.com/rocedu/p/6371315.html#SECCODESTANDARD ...