java‘小秘密’系列(三)---HashMap

java基础系列

基本概念

  • 节点: Node<Key,Value>,存放key和value
  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final int hash;
  3. final K key;
  4. V value;
  5. Node<K,V> next;
  6. }
  • 键值对数组: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

  • 默认加载因子
  1. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  • node 数组
  1. transient Node<K,V>[] table;
  • 键值对数目,不是table的长度
  1. /**
  2. * The number of key-value mappings contained in this map.
  3. */
  4. transient int size;
  • 阈值
  1. /**
  2. * The next size value at which to resize (capacity * load factor).
  3. *
  4. * @serial
  5. */
  6. //阈值
  7. int threshold;
  • 加载因子
  1. /**
  2. * The load factor for the hash table.
  3. *
  4. * @serial
  5. */
  6. //加载因子
  7. final float loadFactor;

构造方法

  • 传入初始容量和加载因子
  1. public HashMap(int initialCapacity, float loadFactor) {
  2. if (initialCapacity < 0)
  3. throw new IllegalArgumentException("Illegal initial capacity: " +
  4. initialCapacity);
  5. if (initialCapacity > MAXIMUM_CAPACITY)
  6. initialCapacity = MAXIMUM_CAPACITY;
  7. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  8. throw new IllegalArgumentException("Illegal load factor: " +
  9. loadFactor);
  10. this.loadFactor = loadFactor;
  11. this.threshold = tableSizeFor(initialCapacity);
  12. }
  • 传入初始容量,使用默认的加载因子
  1. public HashMap(int initialCapacity) {
  2. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  3. }
  • 无参数,默认容量和加载因子
  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  3. * (16) and the default load factor (0.75).
  4. */
  5. public HashMap() {
  6. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  7. }

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

put方法

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  5. boolean evict) {
  6. Node<K,V>[] tab; Node<K,V> p; int n, i;
  7. if ((tab = table) == null || (n = tab.length) == 0)
  8. n = (tab = resize()).length;
  9. if ((p = tab[i = (n - 1) & hash]) == null)
  10. tab[i] = newNode(hash, key, value, null);
  11. else {
  12. Node<K,V> e; K k;
  13. if (p.hash == hash &&
  14. ((k = p.key) == key || (key != null && key.equals(k))))
  15. e = p;
  16. else if (p instanceof TreeNode)
  17. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  18. else {
  19. for (int binCount = 0; ; ++binCount) {
  20. if ((e = p.next) == null) {
  21. p.next = newNode(hash, key, value, null);
  22. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  23. treeifyBin(tab, hash);
  24. break;
  25. }
  26. if (e.hash == hash &&
  27. ((k = e.key) == key || (key != null && key.equals(k))))
  28. break;
  29. p = e;
  30. }
  31. }
  32. if (e != null) { // existing mapping for key
  33. V oldValue = e.value;
  34. if (!onlyIfAbsent || oldValue == null)
  35. e.value = value;
  36. afterNodeAccess(e);
  37. return oldValue;
  38. }
  39. }
  40. ++modCount;
  41. if (++size > threshold)
  42. resize();
  43. afterNodeInsertion(evict);
  44. return null;
  45. }
  • 直接用伪代码表示
  1. put()
  2. {
  3. index=[hash(key)&(captity-1)]----下标的最大值为captity-1,进行与运算后最终的结果小于等于最大下标
  4. if(table[index])==null)
  5. 直接添加node
  6. else
  7. {
  8. if(ptreenode)
  9. {
  10. 直接将节点添加到红黑树
  11. }
  12. else
  13. {
  14. 如果不是红黑树是链表
  15. if(p的键值==key)
  16. 覆盖value
  17. else
  18. {
  19. 遍历链表
  20. if(有对应的key)
  21. {
  22. 覆盖value
  23. }
  24. else
  25. {
  26. 添加到链表
  27. if(链表长度>8)
  28. {
  29. 将链表转化为红黑树
  30. }
  31. }
  32. }
  33. }
  34. if(大小大于阈值)
  35. {
  36. 容量加倍,重新构造
  37. }
  38. }
  39. }

get方法

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. return (e = getNode(hash(key), key)) == null ? null : e.value;
  4. }
  5. final Node<K,V> getNode(int hash, Object key) {
  6. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  7. if ((tab = table) != null && (n = tab.length) > 0 &&
  8. (first = tab[(n - 1) & hash]) != null) {
  9. //如果链表的第一个节点是的键和要查找的键相等,那么返回该node
  10. if (first.hash == hash && // always check first node
  11. ((k = first.key) == key || (key != null && key.equals(k))))
  12. return first;
  13. //如果不是的,看该节点是不是树节点,是的话,用树的方法查找节点,如果不是的按链表的方式查找
  14. if ((e = first.next) != null) {
  15. if (first instanceof TreeNode)
  16. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  17. do {
  18. if (e.hash == hash &&
  19. ((k = e.key) == key || (key != null && key.equals(k))))
  20. return e;
  21. } while ((e = e.next) != null);
  22. }
  23. }
  24. return null;
  25. }

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

  1. if ((p = tab[i = (n - 1) & hash]) == null)
  2. tab[i] = newNode(hash, key, value, null);
  • 存放node到table数组的时候,他的下标是通过(n-1)&hash计算出来的(数组长度-1 和 key的hash的值相与,最后结果小于等于长度-1),n为table的长度。
  • 显然这种效果和hash%n-1的效果很类似,而实际上,当长度为2的n次幂的时候,(n-1)&hash的效果就是hash%n,而前者是位运算,速度会快很多
  • 长度为2的n次幂的时候,n-1的二进制全是1,那么这样可以减少碰撞的机率,比如如果有一位是0,那么如果与的是1或者0,结果是一样的,那么这样就加大了碰撞的机率

负载因子

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

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

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

java‘小秘密’系列(三)---HashMap的更多相关文章

  1. java基础系列(三)---HashMap

    java基础系列(三)---HashMap java基础系列 java基础系列(一)---String.StringBuffer.StringBuilder java基础系列(二)---Integer ...

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

    java基础解析系列(三)---HashMap java基础解析系列 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  3. java‘小秘密’系列(二)---Integer

    java'小秘密'系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java'小秘 ...

  4. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  5. Java 集合系列 11 hashmap 和 hashtable 的区别

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  6. java集合系列之HashMap源码

    java集合系列之HashMap源码 HashMap的源码可真不好消化!!! 首先简单介绍一下HashMap集合的特点.HashMap存放键值对,键值对封装在Node(代码如下,比较简单,不再介绍)节 ...

  7. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  8. Java爬虫系列三:使用Jsoup解析HTML

    在上一篇随笔<Java爬虫系列二:使用HttpClient抓取页面HTML>中介绍了怎么使用HttpClient进行爬虫的第一步--抓取页面html,今天接着来看下爬虫的第二步--解析抓取 ...

  9. Java集合系列之HashMap

    概要 第1部分 HashMap介绍 HashMap简介 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Clo ...

随机推荐

  1. Hibernate一对多实例

    本文向大家介绍Hibernate实例一对多的情况,可能好多人还不了解Hibernate实例一对多,没有关系,下面通过一个实例来帮助您理解Hibernate实例一对多,希望本文能教会你更多东西. 先看由 ...

  2. (转)Java中equals和==的区别

    java中的数据类型,可分为两类:  1.基本数据类型,也称原始数据类型.byte,short,char,int,long,float,double,boolean    他们之间的比较,应用双等号( ...

  3. JavaScript系统学习小结——Object类型、Array类型

    今天学习JavaSript中引用变量中的Object类型和Array类型: 1. Js中大多数引用类型值都是Object类型的实例,Object类型在应用程序中存储和传输数据时,是非常理想的选择: 创 ...

  4. 【CSS】div父容器根据子容器大小自适应

    Div即父容器不根据内容自动调节高度,我们看下面的代码: <div id="main"> <div id="content"></ ...

  5. Node.js 入门:Express + Mongoose 基础使用

    前言 Express 是基于 Node.js 平台的 web 应用开发框架,在学习了 Node.js 的基础知识后,可以使用 Express 框架来搭建一个 web 应用,实现对数据库的增删查改. 数 ...

  6. 41. leetcode 53. Maximum Subarray

    53. Maximum Subarray Find the contiguous subarray within an array (containing at least one number) w ...

  7. Linux系统C语言socket tcp套接字编程

    1.套接字的地址结构: typedef uint32_t in_addr_t; //32位无符号整数,用于表示网络地址 struct in_addr{ in_addr_t s_addr; //32位 ...

  8. Base:一种 Acid 的替代方案

    原文链接: BASE: An Acid Alternative 数据库 ACID,都不陌生:原子性.一致性.隔离性和持久性,这在单台服务器就能搞定的时代,很容易实现,但是到了现在,面对如此庞大的访问量 ...

  9. 微信小程序开发之微信支付

    微信支付是小程序开发中很重要的一个环节,下面会结合实战进行分析总结 环境准备 https服务器 微信小程序只支持https请求,因此需要配置https的单向认证服务(请参考 另一篇文章https受信证 ...

  10. 关于Wifi室内定位应用中的一些问题:

    公司目前在办公室内布设了一套室内定位的实验环境,用的是华为路由器,采用的算法是基于信号强度的RSSI算法.公司目前希望能使用这套设备得到无线网络覆盖范围下的所有移动设备(对应每个人)的MAC地址,同时 ...