切入点,说说几个典型的:

HashMap/ Hashtable/TreeMap

其中HashMap其实都被讲烂了,但是呢,咱还是说一说。

说道HashMap,我个人觉得,先从构造入手,毕竟先new,才不会空指针。也可以从静态入手。

先看看HashMap中有哪些常量

/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;

挨个聊聊

一、DEFAULT_INITIAL_CAPACITY

这里为什么要1<<4??????? 然后又名16  ;装逼。。。

其实也不是装逼,目的是:为了运算迅速,因为上述描述中,要求容量为2的整数次幂。至于为什么要是2的整数?并且我们观察最大容量也是2的整数次幂????

为什么呢?

其实原因就在于减少hash碰撞;得到一个充分的散列数组

怎么减少呢?举例说明:

代码中确定一个元素在数组中的位置,是通过该元素的hash值 & newCap-1 得到数组下标。那么我们示例看看,该运算计算出的下标是什么:

1.hash算法

注:以上截图来自知乎大佬(说实话我没看懂,应该本质就是对质数的特性进行运算)

关于上述解释:s为什么取值为131,使用前面的字符串数组中,取得字符的ascii码,数值<=127,为了尽可能保证获取的hash的唯一性(减少hash冲突),因此需要让s是一个大于127的质数,二为了提高散列的密度,又要使得s尽可能小,因此大于127的最小质数,就是131。这个值就是最佳的散列质数和散列密度。

1.1解决hash冲突

此处以hashMap中讲一讲链地址法:

将冲突的元素建立一个链表,将两个冲突的元素进行链表尾部增加。(后面会讲讲HashMap的数据结构,以及jdk8改动)

此处不深入。

二、

hash表的默认负载因子

什么是负载因子?负载因子是表示hash表中元素的填满的程度,如果负载因子越大,那么对于hash表中填满的元素就越多,好处就是:空间利用率高了,但是冲突的机会加大了。反之,负载因子越小,填满的元素越少,好处就是,冲突的机会减小了,但是空间的浪费就越多了。

冲突的机会越大,那么我寻址的成本越高,反之,查找的成本越小,因而查找的时间越小。

那么就得权衡一种阈值,泽中的值,官方给出的就是0.75f

换句话来说,就是当数组中的数据达到数组的容量的0.75的时候再扩容,提供了空间利用率,效率避免hash冲突,降低数据结构横向纵向。

( Java 是 0.75,Go 中是 0.65,Dart 中是0.8,python 中是0.762)

此处分析一下为什么是负载因子越大,填满就越多,越多就容易冲突???????

<p>As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs.  Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the <tt>HashMap</tt> class, including <tt>get</tt> and <tt>put</tt>).  The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations.  If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.

<p>通常,默认的负载系数(.75)在时间和空间成本之间提供了一个很好的折中方案。 较高的值会减少空间开销,但会增加查找成本(在<tt> HashMap </ tt>类的大多数操作中都得到体现,包括<tt> get </ tt>和<tt> put </ tt>)。 设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以最大程度地减少再哈希操作的次数。 如果初始容量大于最大条目数除以负载因子,则将不会发生任何哈希操作。

这也是HashMap中的原话。

举个栗子,例如,我们有10间房间,我们有5个人需要住进去,那么第一个人好办,直接挑一间住;但是第5个人不好搞。有4/10的概率会碰到这间房间已经被住了人,那么怎么办呢?他需要再找下一间房间,那么但是又有3/9的概率碰到下一间房间也被住了人。这就是“hash碰撞,冲突”。当然此处在hash运算中不会是随机找房间。是有运算的,这也是减少了房间冲突的概率,例如,我们按照年龄来住房间顺序。这样就降低了房间冲突概率,较好散列。但是呢又会出现两个人年龄一样的概率。这个时候又得碰撞了。所以,在数据量达到一定范围,hash碰撞是会产生,所以需要制定碰撞解决方法。上述也说了。那么如果我们有10000间房间,有5个人住进去,那么这基本不冲突了。但是,10000间房间,这空间成本。。。。。

这是一个很简单的例子,可能不是很正确表达hash的原理。只是简单提供思路

这是笔者的原话。

三、阈值

为什么将上述三个静态常量称为阈值呢?原因在于他们是用于比较大小居多。

因为TreeNode的大小大约是常规节点的两倍,所以我们仅在垃圾箱包含足以保证使用的节点时才使用它们(请参阅TREEIFY_THRESHOLD)。 当它们变得太小(由于移除或调整大小)时,它们会转换回普通纸槽。 在使用具有良好分布的用户hashCode的用法中,很少使用树箱。 理想情况下,在随机hashCodes下,bin中节点的频率遵循泊松分布

(http://en.wikipedia.org/wiki/Poisson_distribution),默认调整大小阈值为0.75,平均参数约为0.5,尽管 由于调整粒度,差异很大。 忽略方差,列表大小k的预期出现次数是(exp(-0.5)* pow(0.5,k)/ factorial(k))。 第一个值是:

0:0.60653066

1:0.30326533

2:0.07581633

3:0.01263606

4:0.00157952

5:0.00015795

6:0.00001316

7:0.00000094

8:0.00000006

更多:不到一千万分之一

解释上述注释的原因是因为“树化阈值”为什么是8?上述表明了计算规则

(exp(-0.5)* pow(0.5,k)/ factorial(k))

可以看出,oracle小老弟将通过上述公式,计算出当桶中出现元素个数出现的频率的概率,其原理就是按照泊松分布计算的。这里科普一下

Poisson分布,是一种统计与概率学里常见到的离散概率分布,由法国数学家西莫恩·德尼·泊松(Siméon-Denis Poisson)在1838年时发表。

也就是说,非独立分布数据。

先说说其他优势:

红黑树的平均查找长度是log(n),如果长度为8,平均查找长度为log(8)=3,链表的平均查找长度为N/2,也就是等于8/2=4,那么这个时候:红黑树的查找效率是优于链表的,这个时候是需要树化的。

那么如果链表长度为6,那么红黑树的平均查找长度为log(6)=2.6,链表的平均查找长度为6/2=3。两者想差不大,但是转为树化结构和生成树的时间也会消耗,所以并不最优。这是网络上的答案,也是从查找效率上分析的。似乎不是oracle的想法。

那么回到正题,hash树化阈值(yu zhi)为什么是8,不是9,不是7呢?就是在于考虑到空间利用率和散列效率,在元素为8的时候,概率是比7低很多,然后比9就没必要了。

当元素为8个的时候,概率就基本为0了,0.00000006。。。。。。

那不会有童鞋天天往HashMap里面装千万条数据吧???有的话,也不要紧,我有红黑树。

此处有个问题需要注意,如果有人把对象的hashCode重写,那么将在哈希桶中出现多个元素的概率就会大大增加,例如,将某个元素的hashCode重写为返回1,那么对应1这个hash桶将全部的都是这个元素,将会横向增大hash表的数据。所以oracle引入了链表向红黑树转化的概念。

关于其中的0.5是什么,可能就是类似oracle小老弟计算的平均值。此处不深究

很多小老弟可能有疑问,为什么阈值不是一个值,而是两个值?

在元素大于等于8的时候树化,在小于等于6的时候树退化??????

因为两者如果都是8的话,那么就会陷入“树化”“树退化”来回循环转换的死锁中,如果是定义为7为树退化的阈值,那么在特定情况下,频繁的对同一个哈希桶的链表长度进行7~8之间转换(链表和红黑树来回转换),那么也会导致频繁的树化和树退化的转换。

那么为啥是6?不是5、4、3.呢?

1.比较概率:随着元素个数的减少,出现hash运算在同一个hash桶中的概率就会增加。

2.比较成本,如果一个桶中,曾经元素个数达到9,但是,后续删除到了4,但是树退化阈值为3,然后结构一直为红黑树,那么就会增加查询成本,比如上述讲到:log(N)    和 N/2;那么就可以很清晰对比出log(3)== 0.47712125472,   3/2==1.5。红黑树结点占用空间大,并且在第三个常量。就是定义hash表树化阈值==64。除非说我们重写hashCode为常量。

查询数量虽然红黑树快,但是不利于增删改。这也是一个很大的弊端。那么对于权重来说,更多会把树退化阈值定义为6。

3.聊聊hash运算

这个不能少

Computes key.hashCode() and spreads (XORs) higher bits of hash to lower.  Because the table uses power-of-two masking, sets of hashes that vary only in bits above the current mask will always collide. (Among known examples are sets of Float keys holding consecutive whole numbers in small tables.)  So we apply a transform that spreads the impact of higher bits downward. There is a tradeoff between speed, utility, and quality of bit-spreading. Because many common sets of hashes are already reasonably distributed (so don't benefit from spreading), and because we use trees to handle large sets of collisions in bins, we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

计算key.hashCode()并将哈希的较高位扩展(XOR)到较低位。 由于该表使用2的幂次掩码,因此仅在当前掩码上方的位中发生变化的哈希集将始终发生冲突。 (众所周知的示例是在小表中保存连续整数的Float键集。)因此,我们应用了一种将向下扩展较高位的影响的变换。 在速度,实用性和位扩展质量之间需要权衡。 由于许多常见的哈希集已经合理分布(因此无法从扩展中受益),并且由于我们使用树来处理容器中的大量冲突集,因此我们仅以最便宜的方式对一些移位后的位进行XOR,以减少系统损失, 以及合并最高位的影响,否则由于表范围的限制,这些位将永远不会在索引计算中使用。

从理解来讲,其实这个注释已经讲清楚了。通过二进制运算,将key的hashCode运算并且将其高位扩展至低位。目的是为了提高散列效率。减少冲突频率。方式就是将其hashCode通过无符号向右位运算16位,然后再异或运算。至于为什么是16位,也是了一种较便宜的计算方式,折中损失。这样可以这样理解,就是对于直接使用对象的hashCode值不是很好,因为对于部分的对象,其hashCode值很有可能直接出现冲突(情况不举例了)。那么对于HashMap中的运用,为了有效的避免hash冲突,势必需要对其进行一些运算干扰,让其hash值更散列。减少冲突概率。但是还是有冲突概率。所以有链地址法。jdk8后出现了树化。

这里讲解一下异或,并且这里为什么要使用异或

关于异或的知识:

* A ^ 0 = A,即当0与一个数(0/1)进行异或操作时,结果等于这个数本身,如0 ^ 0 = 0、 0 ^ 1 = 1;

* A ^ 1 = ! A,即当1与一个数(0/1)进行异或操作,结果等于非-此数,或者说取反,如1 ^ 0 = 1, 1 ^ 1 = 0;

所以很好理解,就是将其二进制值打乱,通过与其无符号向右位运算16。通过二进制的特性,将其更效率的散列。减少hash碰撞。

具体运算过程不再赘述,(先调用hashCode(),再与其本身进行无符号右移16位的结果进行异或运算,然后再与(n-1)进行与运算)

下期我们分析一下hashmap源码,不得不说,HashMap是一个强大的工具类,里面全都是高级技术。

今天唠唠HashMap相关推荐

  1. android 让dialog保持在最前_Android 面试进阶指南 —— 唠唠任务栈,返回栈和启动模式...

    Android 面试进阶指南目录 唠唠任务栈,返回栈和启动模式 唠唠 Activity 的生命周期 扒一扒 Context 为什么不能使用 Application Context 显示 Dialog? ...

  2. 唠唠MySQL的join

    准备条件: 建个库: create database test; use test; 建立t1,t2两张表,然后在t1插入100行数据,t2插入1000行数据: CREATE TABLE `t2` ( ...

  3. 微信小程序开发 [00] 写在前面的话,疯狂唠唠

    我总是喜欢在写东西之前唠唠嗑,按照惯例会在博文的开篇写这么一段"写在前面的话",这次却为了这个唠嗑单独开了一篇文,大概预想着要胡说八道的话有点多. 前段时间突然对小程序来了兴趣,说 ...

  4. 随便唠唠 编译时注解 这个技术

    随便唠唠 编译时注解 这个技术 1 => 缘起 公司 部门负责人 突然有一天聊起了 编译时注解这么个名词,说实话,编译时.注解这两个名词非常的耳熟,连在一起对我就有点陌生了.使我不得不坐下来研究 ...

  5. 面试官:唠唠 Activity 的生命周期

    Android 复习笔记目录 唠唠任务栈,返回栈和生命周期 唠唠 Activity 的生命周期 上一篇文章唠了唠 任务栈,返回栈和启动模式,今天来聊一聊同样和 Activity 息息相关的 生命周期 ...

  6. ReXNet:消除表达瓶颈,进来唠唠网络设计那些事

    ReXNet CVPR2021的文章,代码不长,简单复现了一下,重点应该是对网络设计模型的一些思考,进来咱们唠唠网络模型一些思想 基于原Pytorch,更少改动 本文更多是吸收网络设计一些有用的思想 ...

  7. 唠唠(信息熵)一大家子的事

    唠唠(信息熵)一大家子的事 话说林舒是<信道编码>的大牛,最近在看大牛著作<信道编码:经典与现代>,同时也在<模式识别与机器学习>课堂,碰到了同一个理解不好的概念, ...

  8. 英语总结系列(二十六):唠唠我的二月英语历程

    [前言] 一月又一月,陪我唠英语!由于项目要上线所以这个月全职进攻项目:相比之前英语没有每天固定的两个小时了,也就是说只能借助平日里的零碎时间来学英语喽. [二月英语路] 从零碎时间说起,每天零碎时间 ...

  9. java8新特性学习笔记之唠唠“匿名内部类与lambda”

    负一.知道啥是匿名内部类不? 要使用lambda,我觉得你至少得明白匿名内部类是个啥."o -> o.getName"是lambda表达式,"Book::getNa ...

最新文章

  1. Nchain旗下矿池挖出首个BCH区块,“算力战争”真的要来了?
  2. log4j记录不同的日志_使用log4j将不同类型的日志信息记录到不同的文件中
  3. 阿帕奇骆驼遇见Redis
  4. Linux学习笔记(六)
  5. 将iphone中的照片同步到电脑
  6. LeetCode 第 36 场双周赛(304/2204,前13.8%)
  7. 怎么用php写软件老吴p,11.32 php扩展模块装安
  8. android rtsp协议转http协议_Http协议和Https协议
  9. http 1.php,php – Nginx忽略客户端的HTTP 1.0请求并通过HTTP 1.1响应
  10. 陈天石吴翰清顾嘉唯光速对话(汤晓鸥今天没有晒娃)
  11. 用matplotlib中的scatter方法画散点图
  12. deepin上配置eclipse的hadoop开发环境
  13. 区块链会议_2018杭州云栖大会区块链相关_20180919
  14. java获取法定节假日
  15. HrbustOJ 1167 每种面值的货币要多少
  16. 有趣的23000----整理(07)A词根
  17. 在 Excel UiPath 中插入或删除行或列
  18. python医院自动化抢号脚本
  19. php+html+css制作非常好看网站登录与注册界面
  20. IE浏览器不能上网原因及解决方案

热门文章

  1. 奢侈生活:时尚产业拥抱区块链(中篇)
  2. postgresql SQL 优化 -- 理论与原理
  3. MSSQL之五 连接查询与子查询
  4. 数组中存放的多个十六进制数合并成一个十六进制数,并转换成十进制(整形)
  5. 客户体验|审美体验与体验管理
  6. 聚势、聚能、聚流,12月世界燕窝滋补品展|上海燕博会开启健康滋补新未来!
  7. 可用于标记蛋白质216699-36-4,6-ROX,SE,6-羧基-X-罗丹明琥珀酰亚胺酯
  8. 发货快递单号太多,教你如何批量查询快递状态
  9. 点睛挖雷 PSP 1.1 版(For 3.xx)
  10. 欧格电商:差评对店铺有什么影响