当前位置: 首页 > news >正文

Java HashMap 分析四篇连载

转自:http://blog.csdn.net/sheismylife/article/details/7347026

Java的HashMap非常的常用,本篇研究它的实现算法,最后希望计算出内存占用,性能的量化数据,然后得出什么时候使用HashMap,什么时候不能滥用的结论。
HashMap实际上是一个数组,数组里面的每个元素都是一个链表。每个元素在通过put方法放入HashMap中的时候,要按照如下步骤进行:
1.根据该元素自身提供的hashcode计算出散列值,该散列值就是数组的下标
2.将新元素放入该数组位置的链表中

先来看一下数组的定义:

[java]  view plain copy print ?
  1. /** 
  2.      * The table, resized as necessary. Length MUST Always be a power of two. 
  3.      */  
  4.     transient Entry[] table;  

这是一个数组,transient关键字告诉我们它不会参与序列化。既然是一个数组,总有数目上限,也就意味着如果存入HashMap的元素太多,导致数组大小不能够存放所有的链表的时候,数组大小必须要能够调整。所以首先来考察一下数组容量的相关算法。
第一,Entry是什么类型?

[java]  view plain copy print ?
  1. static class Entry<K,V> implements Map.Entry<K,V> {  
  2.         final K key;  
  3.         V value;  
  4.         Entry<K,V> next;  
  5.         final int hash;  
  6.   
  7.         /** 
  8.          * Creates new entry. 
  9.          */  
  10.         Entry(int h, K k, V v, Entry<K,V> n) {  
  11.             value = v;  
  12.             next = n;  
  13.             key = k;  
  14.             hash = h;  
  15.         }  
  16.         ....  
  17.         public final boolean equals(Object o) {  
  18.             if (!(o instanceof Map.Entry))  
  19.                 return false;  
  20.             Map.Entry e = (Map.Entry)o;  
  21.             Object k1 = getKey();  
  22.             Object k2 = e.getKey();  
  23.             if (k1 == k2 || (k1 != null && k1.equals(k2))) {  
  24.                 Object v1 = getValue();  
  25.                 Object v2 = e.getValue();  
  26.                 if (v1 == v2 || (v1 != null && v1.equals(v2)))  
  27.                     return true;  
  28.             }  
  29.             return false;  
  30.         }  
  31.   
  32.         public final int hashCode() {  
  33.             return (key==null   ? 0 : key.hashCode()) ^  
  34.                    (value==null ? 0 : value.hashCode());  
  35.         }  
  36.         ....  

这是一个HashMap类的内部静态类。实现了Map.Entry接口。接受两个模板参数K和V。key和hash一旦在构造函数中被初始化,就不可改变,并且由于有next的存在,Entry可以构成一个单向链表。
比较重要的是equals和hashCode方法。代码先列出来,后面再解释。

第二,初始容量的设定
大多数都在下面的构造函数里面.用于指定的initialCapacity不准小于0,也不能超过最大值。并且最终的capicity必须是2的n次方。还有如果使用了无参数的构造函数,默认会创建一个拥有16个元素的数组。

[java]  view plain copy print ?
  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.   
  11.         // Find a power of 2 >= initialCapacity  
  12.         int capacity = 1;  
  13.         while (capacity < initialCapacity)  
  14.             capacity <<= 1;  
  15.   
  16.         this.loadFactor = loadFactor;  
  17.         threshold = (int)(capacity * loadFactor);  
  18.         table = new Entry[capacity];  
  19.         init();  
  20.     }  

第三,什么时候应该调整数组的大小?
算法是这样,有一个变量size保存了实际数组已经使用了多少个元素,并且如果size的值达到了变量threshold的值,就必须扩充数组的容量。threshold=capicity*loadFactor.capicity是数组最大的容纳元素个数,loadFactor可以在构造函数中制定,否则采用默认值0.75f。capicity的最大值是1<<30(也就是2的30次方,1073741824).由此我们可以看到HashMap最多存放10亿多个链表。
第四,如何调整数组大小?
答案是2倍,很像C++里面的vector的分配策略。

[java]  view plain copy print ?
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.         Entry<K,V> e = table[bucketIndex];  
  3.         table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  4.         if (size++ >= threshold)  
  5.             resize(2 * table.length);  
  6.     }  

第五,为什么数组大小必须是2的倍数?
在后面介绍散列值算法的时候会回答。


相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Leetcode 144. Binary Tree Preorder Traversal
  • 单页web应用是什么?它又会给传统网站带来哪些好处?
  • 深入理解HTML协议
  • BootStrap学习笔记
  • 深入解析 HTML DocumentType 元素
  • Android -- 获取网络数据并将数据存到本地数据库中
  • CSS选择符
  • Android 使用Socket进行通信(Android)
  • PythonNote01_HTML标签
  • CentOS7 搭建python3 Django环境
  • hibernate各种属性配置
  • Flexible 弹性盒子模型之CSS flex-flow
  • Python高手之路【四】python函数装饰器,迭代器
  • 常用的获取随机字符串
  • Eclipse中web项目部署至Tomcat步骤
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 77. Combinations
  • Javascript弹出层-初探
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • React+TypeScript入门
  • SQLServer之创建显式事务
  • Sublime text 3 3103 注册码
  • supervisor 永不挂掉的进程 安装以及使用
  • vue 配置sass、scss全局变量
  • vuex 学习笔记 01
  • Vue官网教程学习过程中值得记录的一些事情
  • 记录一下第一次使用npm
  • 你不可错过的前端面试题(一)
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 湖北分布式智能数据采集方法有哪些?
  • ​ArcGIS Pro 如何批量删除字段
  • #Datawhale AI夏令营第4期#AIGC文生图方向复盘
  • #if和#ifdef区别
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (day 12)JavaScript学习笔记(数组3)
  • (四)鸿鹄云架构一服务注册中心
  • (五)网络优化与超参数选择--九五小庞
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .net dataexcel winform控件 更新 日志
  • .NET4.0并行计算技术基础(1)
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .NET成年了,然后呢?
  • .php结尾的域名,【php】php正则截取url中域名后的内容
  • ?php echo $logosrc[0];?,如何在一行中显示logo和标题?
  • @Bean, @Component, @Configuration简析
  • [100天算法】-x 的平方根(day 61)
  • [APIO2012] 派遣 dispatching
  • [BT]小迪安全2023学习笔记(第29天:Web攻防-SQL注入)
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)
  • [EFI]DELL XPS13 9360电脑 Hackintosh 黑苹果efi引导文件
  • [go 反射] 进阶