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的过程也很巧妙,就是不停的拷贝1n |= 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的次幂,扩张的步骤:

  1. 申请一个容量乘以2的新Node数组。
  2. 遍历table,将原来的元素依次copy到新数组上,这里原来的链表会进行分裂。因为table的扩大,Node的hash值会被多mask一位,所以Node被Hash到的位置也会变化,Node要么保持原位置,要么就是在相对于原位置有一个旧容量大小的偏移。

这里不会将原来的元素重新进行一次hash的过程,重建原来的hash table,这样代价是比较大的。这里就利用了Node位置变化的规律,直接将原来的链表分裂为两个。

虽然已经进行了优化,但是该过程代价还是比较高的,时间复杂度为原table大小+元素数量

table中链表太长如何处理

当Node发生多次冲突,在一个hash值下建了一个很长的链表,这会导致查询的代价越来越大,这里采用了树结构来减轻这种问题。

具体过程是,当某个hash值下的链表超过了阈值,则会采取策略对其长度进行消解。

  1. 如果table还不算大,那么直接对table扩容,链表自然会被分裂到两地。
  2. 策略二,如果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相关推荐

  1. Java容器类研究4:ArrayList

    java.util.ArrayList ArrayList继承自AbstractList,AbstractList为随机访问数据的结构,如数组提供了基本实现,并且提供了Iterator.首先看Abst ...

  2. java容器类4:Queue深入解读

    Collection的其它两大分支:List和Set在前面已近分析过,这篇来分析一下Queue的底层实现. 前三篇关于Java容器类的文章: java容器类1:Collection,List,Arra ...

  3. java容器类继承_JAVA容器 - weslie - OSCHINA - 中文开源技术交流社区

    一. 数组 1.数组是保存一组对象的最有效的方式.但数组有固定的尺寸而受限(p216) 2.数组与其他种类的容器之间的区别有三方面:效率.类型和保存基本类型的能力.在Java中,数组是一种效率最高的存 ...

  4. java容器类3:set/HastSet/MapSet深入解读

    介绍 Set:集合,是一个不包含重复数据的集合.(A collection that contains no duplicate elements. ) set中最多包含一个null元素,否者包含了两 ...

  5. java容器类---概述

    1.容器类关系图 虚线框表示接口. 实线框表示实体类. 粗线框表示最经常使用的实体类. 点线的箭头表示实现了这个接口. 实线箭头表示类能够制造箭头所指的那个类的对象. Java集合工具包位于Java. ...

  6. Java容器类 Collection (set list queue)和map

    参考文献 :http://blog.csdn.net/qq_25868207/article/details/55259978 https://www.cnblogs.com/LittleHann/p ...

  7. Java容器类类库概述

    注:本文讨论是建立在Java 8 基础上的 简介 Java容器类类库是Java提供的有效组织和操作数据的数据结构,其主要用途是"保存对象",并且被划分为两个不同的概念: Colle ...

  8. java容器类的继承结构

    摘要: java容器类的继承结构 Java容器类库定义了两个不同概念的容器,Collection和Map Collection 一个独立元素的序列,这些元素都服从一条或多条规则.List必须按照插入的 ...

  9. Java反射研究(2)

    接Java反射研究(1) 九.调用特定方法 Method m = c1.getMethod("funcname",Class<?>...c);   //funcname ...

  10. Java容器类和包装类

    Java容器类主要有vector和Collection public static void main(String[] args) { // TODO Auto-generated method s ...

最新文章

  1. Android Wi-Fi基本操作
  2. text-indent无效解决方案
  3. Linux进程与线程的区别
  4. 20080823-jsp中include指令与动作的不同
  5. 看雪 2016CrackMe 攻防大赛 - 1-Crack_Me-凉飕飕
  6. 如何查看电脑配置信息_如何查看软件著作权登记的信息?
  7. sql server死锁_如何报告SQL Server死锁事件
  8. 开源性能测试工具JMeter快速入门(二)
  9. linux常见问题解决方法,Ubuntu 下2个常见问题解决方法
  10. les有学计算机的吗,赵欢喜
  11. ambari 搭建hadoop大数据平台系列4-配置ambari-server
  12. Vue.js学习笔记(一) - 起步
  13. VC利用GDI+显示透明的PNG图片
  14. 在Vmware中安装archlinux(2008.3core)的流程与心得
  15. aix系统服务器限制端口访问,aix系统怎么查看端口是否开启
  16. 做H5页面用什么软件比较好?
  17. with在python中啥意思_python中with的用法
  18. 微信支付分(三)--完结支付分订单
  19. pyautogui自动化控制鼠标和键盘操作
  20. Linux系统管理----用户权限

热门文章

  1. 决策树算法中导致递归返回的三种情况
  2. python3.6下载opencv_ubuntu16.04+anaconda3+python3.6安装OpenCV3
  3. 深度学习笔记(五):LSTM
  4. 【生信进阶练习1000days】day16~day22-RNA-seq data analysis with limma edgeR and Glimma
  5. 【16年浙江省赛 B ZOJ 3937】More Health Points【树上dfs、斜率优化dp、动态维护下凸壳】
  6. lc滤波器是利用电感的感抗_电感器在电路中的应用特性
  7. Django MySQL 多用户_django使用多个数据库的方法实例
  8. 简单的避免idea自动导入 *
  9. 偶们院就业相关政策及问题解答----吃面
  10. win10下rdlc报表在vs(visual studio)中中文显示小方块的批量处理解决方法