Java容器类研究8:HashMap
java.util.Map
Map中的自我引用
需要小心用易变的对象作为Map的key,这会导致Map的行为无法预测。Map也不可以将自己作为key,可以作为value,但是会导致equals和hashCode方法不是well defined.
Map中有些操作涉及递归的遍历,如果Map自我引用,则有可能出现异常。这些方法包括:clone,equals,hashCode和toString。
Map可以将null作为key和value
这是Map接口自己默认实现的一个获取指定key的value,当没有该key时可以获取一个默认值得方法。这就给用户提供了更灵活的选择来处理map中没有存这个key的情况。这里比较有意思的一点是在用v = get(key)
判断一次后,为什么又用containsKey(key)
再判断一次,因为有的map中是允许存null作为value的,所以有key在Map中,但是value为null的情况。
default V getOrDefault(Object key, V defaultValue) {V v;return (((v = get(key)) != null) || containsKey(key))? v: defaultValue;}
HashMap
如何求int型数的最近2次幂
HashMap中,用户可以初始化容量,但是HashMap中容量皆为2的次幂,所以会把用户预设的值先转为最近的2的次幂。tableSizeFor方法求出的结果总是大于或等于cap,且最接近cap的一个2的次幂。当然,对于大于2^30
的数,会返回-2147483648,所以这里会判断n为负数的情况,同时,设置了HashMap的最大容量应该为2^30
(MAXIMUM_CAPACITY)。
有意思的是经过如下几步就能求得一个2的次幂,下面方法所做的是先求得一个2^n-1
,然后+1。获得一个2^n-1
的过程也很巧妙,就是不停的拷贝1
。n |= n >>> x;
将前x位的1
拷贝到了接着的x位。通过几步位操作就获得了目标值,不得不赞叹开发者的聪明。
static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}
HashMap的table
HashMap的table就是一个Node的数组,大小一致保持2的次幂。Node就是HashMap中存储的元素,它有哈希值、key、value和存储下一个Node的next属性。处理冲突的方法是闭哈希方法,也就是有相同的hash值的Node会用链表串起来。
HashMap中的hash值如何计算
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
将key的hashCode的高位和低位部分通过异或合并,使得低位和高位的值都能在hash时发挥作用。因为哈希表会用一个掩码来取hashCode的后面一段,如不采用上述方法,前面一部分的hashCode就被忽略了。如果一组key恰巧只在前段的hash值有所差异,而后段都是相同的,则会出现大量的冲突,这种情况在实际中是很可能存在的。
如何将Node哈希到table中
公式如:table[node.hash & (capacity - 1)] = node
。node的hash值是key在上述hash()
方法中处理过的值,通过与当前容量-1进行mask,直接获取到哈希表的位置。
HashMap的table的扩张
如上所述,table大小保持2的次幂,扩张的步骤:
- 申请一个容量乘以2的新Node数组。
- 遍历table,将原来的元素依次copy到新数组上,这里原来的链表会进行分裂。因为table的扩大,Node的hash值会被多mask一位,所以Node被Hash到的位置也会变化,Node要么保持原位置,要么就是在相对于原位置有一个旧容量大小的偏移。
这里不会将原来的元素重新进行一次hash的过程,重建原来的hash table,这样代价是比较大的。这里就利用了Node位置变化的规律,直接将原来的链表分裂为两个。
虽然已经进行了优化,但是该过程代价还是比较高的,时间复杂度为原table大小+元素数量,
table中链表太长如何处理
当Node发生多次冲突,在一个hash值下建了一个很长的链表,这会导致查询的代价越来越大,这里采用了树结构来减轻这种问题。
具体过程是,当某个hash值下的链表超过了阈值,则会采取策略对其长度进行消解。
- 如果table还不算大,那么直接对table扩容,链表自然会被分裂到两地。
- 策略二,如果table已经很大了,扩容table已经不可取,那么就采用红黑树结构转化链表。
红黑树的创建不再详述。需要知道,这里采用的是二叉搜索树,进行比较的是每个hash值,不是key的直接比较。红黑树的根就是table中第一个节点。原始链表会先变成双向链表,以保存前后关系,然后再变成树结构。虽然由链表转化成了树结构,但是每个节点仍然保存了链表的前后关系,所以可以迅速的从树结构退化为链表结构。从TreeNode的属性中可以看出其既有树结构的left、right、parent关系,又有prev、next(继承)的双向链表关系。
TreeNode<K,V> parent; // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev; // needed to unlink next upon deletion
HashMap的KeySet、Values和EntrySet
HashMap用KeySet来提供一个key的集合视角,KeySet并没有再申请一个空间来存储这些key,而是将所有的方法建立于HashMap的方法之上。KeySet提供了删除key的操作,该操作会映射到其HashMap之上,最后就是在HashMap中删除了该元素。KeySet没有提供添加元素的方法,因为HashMap需要的是key和value,而KeySet只能添加key,方法不能映射到HashMap上。
同样,HashMap提供了value的集合视角Values,EntrySet提供了Node集合的视角,原理类似。
HashMap的Spliterator
在HashMap中,分为key、value和node三种Spliterator,实现原理都是类似的。HashMap的Spliterator的划分不是针对元素的直接划分,而是对table这个数组的划分,这样更为简单。划分的策略也很简单,采用的二分法。
Java容器类研究8:HashMap相关推荐
- Java容器类研究4:ArrayList
java.util.ArrayList ArrayList继承自AbstractList,AbstractList为随机访问数据的结构,如数组提供了基本实现,并且提供了Iterator.首先看Abst ...
- java容器类4:Queue深入解读
Collection的其它两大分支:List和Set在前面已近分析过,这篇来分析一下Queue的底层实现. 前三篇关于Java容器类的文章: java容器类1:Collection,List,Arra ...
- java容器类继承_JAVA容器 - weslie - OSCHINA - 中文开源技术交流社区
一. 数组 1.数组是保存一组对象的最有效的方式.但数组有固定的尺寸而受限(p216) 2.数组与其他种类的容器之间的区别有三方面:效率.类型和保存基本类型的能力.在Java中,数组是一种效率最高的存 ...
- java容器类3:set/HastSet/MapSet深入解读
介绍 Set:集合,是一个不包含重复数据的集合.(A collection that contains no duplicate elements. ) set中最多包含一个null元素,否者包含了两 ...
- java容器类---概述
1.容器类关系图 虚线框表示接口. 实线框表示实体类. 粗线框表示最经常使用的实体类. 点线的箭头表示实现了这个接口. 实线箭头表示类能够制造箭头所指的那个类的对象. Java集合工具包位于Java. ...
- Java容器类 Collection (set list queue)和map
参考文献 :http://blog.csdn.net/qq_25868207/article/details/55259978 https://www.cnblogs.com/LittleHann/p ...
- Java容器类类库概述
注:本文讨论是建立在Java 8 基础上的 简介 Java容器类类库是Java提供的有效组织和操作数据的数据结构,其主要用途是"保存对象",并且被划分为两个不同的概念: Colle ...
- java容器类的继承结构
摘要: java容器类的继承结构 Java容器类库定义了两个不同概念的容器,Collection和Map Collection 一个独立元素的序列,这些元素都服从一条或多条规则.List必须按照插入的 ...
- Java反射研究(2)
接Java反射研究(1) 九.调用特定方法 Method m = c1.getMethod("funcname",Class<?>...c); //funcname ...
- Java容器类和包装类
Java容器类主要有vector和Collection public static void main(String[] args) { // TODO Auto-generated method s ...
最新文章
- Android Wi-Fi基本操作
- text-indent无效解决方案
- Linux进程与线程的区别
- 20080823-jsp中include指令与动作的不同
- 看雪 2016CrackMe 攻防大赛 - 1-Crack_Me-凉飕飕
- 如何查看电脑配置信息_如何查看软件著作权登记的信息?
- sql server死锁_如何报告SQL Server死锁事件
- 开源性能测试工具JMeter快速入门(二)
- linux常见问题解决方法,Ubuntu 下2个常见问题解决方法
- les有学计算机的吗,赵欢喜
- ambari 搭建hadoop大数据平台系列4-配置ambari-server
- Vue.js学习笔记(一) - 起步
- VC利用GDI+显示透明的PNG图片
- 在Vmware中安装archlinux(2008.3core)的流程与心得
- aix系统服务器限制端口访问,aix系统怎么查看端口是否开启
- 做H5页面用什么软件比较好?
- with在python中啥意思_python中with的用法
- 微信支付分(三)--完结支付分订单
- pyautogui自动化控制鼠标和键盘操作
- Linux系统管理----用户权限
热门文章
- 决策树算法中导致递归返回的三种情况
- python3.6下载opencv_ubuntu16.04+anaconda3+python3.6安装OpenCV3
- 深度学习笔记(五):LSTM
- 【生信进阶练习1000days】day16~day22-RNA-seq data analysis with limma edgeR and Glimma
- 【16年浙江省赛 B ZOJ 3937】More Health Points【树上dfs、斜率优化dp、动态维护下凸壳】
- lc滤波器是利用电感的感抗_电感器在电路中的应用特性
- Django MySQL 多用户_django使用多个数据库的方法实例
- 简单的避免idea自动导入 *
- 偶们院就业相关政策及问题解答----吃面
- win10下rdlc报表在vs(visual studio)中中文显示小方块的批量处理解决方法