HashMap

什么是HashMap

HashMap是线程不安全的常用的双列集合,里面存的是key-value键值对组成的一个个entry, key和value都可以为null,但最多只有一个key可以为null,jdk8及之后对于Entry数组的初始化延迟到了第一次put操作时 ;存入map中的key-value  其中value 一定要重写equals方法和hashcode方法

数据结构

1.7底层:数组+链表

1.8底层:数组+链表+红黑树

具体来说存的是一个个的Entry,其中每个Entry中又有:Key、Value、Hash值、Next指针

为什么引入红黑树

哈希冲突过多时,链表过长,性能会下降,时间复杂度为On,引入红黑树之后当链表过了阈值8之后会发生树化,性能为Ologn,反树化阈值为6

树化条件

数组长度大于等于64 并且 某条链表长度大于树化阈值8   那么这条链表会变为红黑树

扩容时机

第一种情况:当存入hashmap中的元素超过阈值之后扩容为原先长度的2倍

第二种情况:当存入元素未超过阈值时,并且数组长度不超过64,但某条链表上元素个数超过树化阈值8时会发生扩容为原来的2倍

计算下标方法

根据k-v的k计算下标,首先调用k的hashcode方法得到一个值h 然后将h与h无符号右移16位之后的值进行异或运算  (这样主要是为了让高位也参与,进而减少哈希冲突),最后得到一个二次哈希值,然后将这个二次哈希值与 容量-1  进行与运算

负载因子为什么是0.75

什么是哈希冲突,遇到哈希冲突怎么办

两个对象调用的 hashCode 方法计算的哈希值经哈希函数算出来的地址被别的元素占用;比如有俩元素都要存入map,根据他们各自的key计算出来的下标相同,也就是他们想存入同一个位置;

解决办法:

开放定址法:具体分为 线性探查法、平方探查法、双散列函数探查法   ,简单说就是当发生哈希冲突时,按一定的规则找到一个空闲的单元,把发生冲突的元素放入。 比如说线性探查法,就是 查看发生冲突的单元的下一个单元是否为空。

拉链法:将哈希值相同的元素,构成一个链表

再次哈希法:当哈希冲突时,再调用另一个哈希函数计算,直到不再冲突。

建立公共溢出区将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区

数组容量为何是2的次幂

1.为了hash%n == hash&(n-1)                  //为了 位运算的成立,位运算效率更高

2.假设现在数组的长度 length 可能是偶数也可能是奇数

length 为偶数时,length-1 为奇数,奇数的二进制最后一位是 1,这样便保证了 hash &(length-1) 的最后一位可能为 0,也可能为 1(这取决于 h 的值),即 & 运算后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性。

例如 length = 4,length - 1 = 3, 3 的 二进制是 11

若此时的 hash 是 2,也就是 10,那么 10 & 11 = 10(偶数位置)

hash = 3,即 11 & 11 = 11 (奇数位置)

而如果 length 为奇数的话,很明显 length-1 为偶数,它的最后一位是 0,这样 hash & (length-1) 的最后一位肯定为 0,即只能为偶数,这样任何 hash 值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间

为什么HashMap线程不安全

Jdk1.7的时候在扩容时候调用resize方法,此时并发容易形成环形链表 有可能获取这个下标处的元素时候造成死循环;而jdk1.8的时候修复了死循环的问题 ,但有可能会出现数值覆盖(俩线程同时发现某个桶没有元素,A线程写了他的值进来,会被B覆盖掉)

HashMap的扩容机制

懒加载,首次put的时候扩容为长度16的数组

首先经过一些判断,计算出新的数组大小和扩容阈值,比如说判断数组大小是否已经达到int的最大值,如果是,就把扩容阈值调整为int的最大值,如果不是就把新容量设置为旧容量的2倍,新阈值设置为旧阈值的2倍。然后开始将旧表中的节点转移到新表中。每个桶依据三种情况: 单个元素、已经形成红黑树、已经形成链表。

单个元素:依据原先的hash值重新与新表长度-1进行&运算寻找新的下标 :要么 旧位置 要么 旧位置+旧容量

红黑树:下次再写..

链表:依次判断每个节点 属于 低位链表还是高位链表,也就是旧位置的 和  旧位置+旧容量的  分别放入对应的桶中

重点就是:

如何寻找新下标 (注意,扩容之后移动元素 是不用重新计算哈希值的 只需计算新下标):

不需要重新计算哈希值,要么 旧位置 要么 旧位置+旧容量

比如说俩元素的二次哈希值分别为 0001 0101 与 0000 0101 ,那么他们与长度16的数组 -1之后 的0000 0101 进行&运算得到的下标都是 0000 0101 也就是下标为5的位置,如果每次数组都扩容二倍,16变为32  n-1就变成了  0001 0101  这俩元素进行&运算之后分别变成了 0001 0101 和 0000 0101 也就是下标分别是 5+16 和 5

如何遍历HashMap

  1. 可以调用map的entrySet方法获取所有的entry 然后使用增强for循环遍历每个entry的同时调用entry的getKey方法或者getValue方法获取key-value。  (推荐使用, 而且遍历Set集合的适合 可以采用迭代器方法)
  2. 可以调用map的keySet方法获取所有的key为一个Set单列集合,利用Set的迭代器遍历key调用get方法获取每个key的value

常用的方法

put(k,v) 向map中存入键值对

remove(k)   删除map中键为k的数据

get(k)  根据k取出对应的value

containsKey(k)   判断集合中是否存在键为k的数据

size()   返回map集合中元素数量

isEmpty()   判断map中是否有数据

entrySet()、 keySet() 返回 entry组成的Set集合 或者 返回key组成的Set集合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值