Hashmap

常见的Hash表结构有:

布隆过滤器、bitmap、HashSet、HashMap、ConcurrentHashMap

JDK的HashMap

HashMap不能由get是否为空来判断是否存在某个键

如果key存在,那么覆盖,不存在则存入整个键值对。

HashMap的一些参数

默认初始化长度为16

默认负载因子0.75

当table被使用75%以上时会二倍扩容,是效率和空间的折中,比如ArrayList扩容1.5因为扩容代价比较小,HashMap需要reHash等等。

transient Set<Map.Entry<K,V>> entrySet; 用于记录entrySet;keySet,valueSet可以通过它的get方法获得,此外,这个属性,只有在第一次调用才会加载。

数组中链表超过8转红黑树,为什么是8?

源码中有一段注释的内容和目的都是为了解释在java8 HashMap中引入Tree Bin(也就是放入数据的每个数组bin从链表node转换为red-black tree node)的原因

理解:
  1. 链表占据的空间小一半:在1-8的时候使用链表,那么时间复杂度其实是O1的,不仅数据结构占用空间是红黑树的一半;
  2. 红黑树在数量较少时插入耗时:链表插入简单,根据编写人员给的数据,数据少的时候冲突时的插入发生概率较大,红黑树会有平衡,改色等操作,远远没有链表来的快速。
  3. 但是在大量数据冲突的时候,链表的时间复杂度就会达到O(N),尤其在Hash函数较差的时候,所以使用红黑树来有效降低高数据的查询O(logN)。

虽然引入TreeNode但是不会轻易转变为TreeNode(如果存在大量转换那么资源代价比较大),根据泊松分布来看转变是小概率事件,性价比是值得的

泊松分布是二项分布的极限形式,两个重点:事件独立、有且只有两个相互对立的结果。泊松分布是指一段时间或空间中发生成功事件的数量的概率。

对HashMap table[]中任意一个bin来说,存入一个数据,要么放入要么不放入,这个动作满足二项分布的两个重点概念

/* 默认参数,在Hash函数理想的情况下, 每个位置上Hash冲突发生的数据个数。
* 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*/
关于两个Node结构空间复杂度的源码

原注:Because TreeNodes are about twice the size of regular nodes, we use them only when bins contain enough nodes to warrant use(see TREEIFY_THRESHOLD).

TreeNode虽然改善了链表增删改查的性能,但是其节点大小是链表节点的两倍

ConcurrentHashMap两倍
//摘自ConcurrentHashMap源码,上方是Node,下方是TreeNode
//看起来没差多少,但是TreeNode其实是继承了Node的,所以父类的成员变量也是有的。
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;

TreeNode<K,V> parent;  // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;    // needed to unlink next upon deletion
boolean red;
hashmap中2.5倍了
//这里再放上HashMap的,内存更多
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    
//反复横跳继承。。
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

hash的处理

hashCode

hashCode 表示对象在 hash 表中的位置,对于同一个对象来说,多次调用,返回相同的 hashCode。

  • 如果 Object.equal () 相等,Object.hashCode () 也必然相等。重写时也建议保证此特性。
  • 如果 Object.equal () 不相等,这并不要求 Object.hashCode () 也返回不同值。如果真出现这种情况,最好优化代码,充分利用 hash 表的性能。

重写hashCode:如果你重载了equals,比如说是基于对象的内容实现的,而保留hashCode的实现不变,那么很可能某两个对象明明是“相等”,而hashCode却不一样。

确定位置

数组结构内通过(n-1)&hash取余,取hash值对于当前容量的余。

Key–Hash—取模—>存储

判断key有三个部分
((k = p.key) == key || (key != null && key.equals(k)))
时间复杂度

O1,On,Ologn(红黑树)

Hashmap缺点:

速度快;但是存不了大数据,线程不安全的,扩容的时候会用空间的操作,不支持多线程,主要是put的时候。

HashTable:

线程安全。继承自Dictionary类实现了Map。方法是同步的,而HashMap中的方法缺省情况下是非同步的。

key和value都不能有null值。

ConcurrentHashMap:

分段锁(JDK1.7),不会影响整个table,只会影响Hash值一样的。

CAS加synchronized(JDK1.8) 一整个数组,数组是以每个结点数据为桶上锁的。读不上锁,写上锁,锁的粒度更小,读写锁也加强了读效率

Hash(散列函数)

数组是简单的Hash,但没有数组就没有散列表,数组的散列函数为:y=x。

  1. 直接寻址法
  2. 余数法
  3. 数字分析法
  4. 平方取中法
  5. 折叠法
  6. 随机数法
Java中的Hash函数
int hash(Object key){
    int h = key.hashCode();
    //无符号右移,为了获取高位的信息
    return (h^(h>>>16))&(capacity - 1);//cap为散列表大小
}
为什么HashMap要二倍扩容

首先,怎么将一个大数据量的hash值放入一个较短的数组中呢?

很显然,取余是一个很好的办法,它能保留原有的hashcode的分散度,比如说,你使用除法,hashcode小的时候一下子就被压缩成可能都是0了。

然后,位运算由于计算机本身的特性,与门,是计算效率很高的计算方法,leetcode中也可以用到。比如说八皇后的问题,就有通过位运算优化的方式,效果上佳。所以这个hash运算是很靠谱的。

其次,假设当前容量是默认值16(10000),那么通过按位与,来取余就是1111与上hash,1与上0和1都能保留该数据散列分布的特性完美取余,如果换成了15来取余,那么1110与上任何二进制数,个位数上的数字永远是0。那么得到的十进制数也将永远是偶数,这时候,恐怖的事情就来了,由于默认负载因子是0.75。而我们得到的hash值只有偶数,也就是说,该hash数组的负载最高值也只能到0.5。数组将永远无法扩容。

Hash冲突

两种方法解决Hash冲突

探测(线性寻址)+链表(拉链)

开放寻址:

如果出现散列冲突,就重新探测一个空闲位置,放入。

Hash函数不变,在寻找对应值时,额外进行比较,如果值不对应或者有delete标志,再向下探测

装不下:扩容

Thread中的成员变量,也是threadLocal中的静态内部类threadLocalMap就采用了探测法。末尾的探测会回到头部。
链路地址(更常用):

寻址后找到的是链表,即不存值,存链表指针 。

链表插入、删除快,但是查询慢。二叉搜索树可能会退化成链表,所以加入了红黑树。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值