HashMap 知识点整理

数据结构中:
数组 在内存中是连续存储的,空间复杂度较大,查询可以根据索引查找,但插入删除困难
链表:查询速度慢。需要遍历整个链表,插入与删除较快,
hashmap由数组和链表组成,又称链表散列
python中的dict,C艹中的unordered_map,都是基于hashmap
hashmap特点
快速储存:get与put速度快;查找快,时间复杂度O(1),

程序员小灰:

从Key映射到HashMap数组的对应位置,会用到一个Hash函数:
index = Hash(“apple”)
hash中定位到桶的位置 是根据Key的hash值与数组的长度取模来计算的
为了增加效率HashMap取模用了位运算,
我们的hash(Object key)算法一个道理,最终的hash值混合了高位和低位的信息,掺杂的元素多了,那么最终hash值的随机性越大,而HashMap的table下标依赖于最终hash值与table.length()-1的&运算,这里的&运算类似于挑包子的过程,自然冲突就小得多了。计算过程如下:

最开始的hashCode: 1111 1111 1111 1111 0100 1100 0000 1010

右移16位的hashCode:0000 0000 0000 0000 1111 1111 1111 1111

异或运算后的hash值: 1111 1111 1111 1111 1011 0011 1111 0101

hashcode的获取:
我们可以看到HashMap中的hash算法是通过key的hashcode值与其hashcode右移16位后得到的值进行异或运算得到的,那么为什么不直接使用key.hashCode(),而要进行异或操作?我们知道hash的目的是为了得到进行索引,而hash是有可能冲突的,也就是不同的key得到了同样的hash值,这样就很容易产业碰撞,如何减少这种情况的发生呢,就通过上述的hash(Object key)算法将hashcode 与 hashcode的低16位做异或运算,混合了高位和低位得出的最终hash值,冲突的概率就小多了。

取模可以改为:hashCode & (length - 1)
小灰给了例子:
下面我们以值为“book”的Key来演示整个过程:
1.计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001。
2.假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111。
3.把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9。
可以说,Hash算法最终得到的index结果,完全取决于Key的Hashcode值的最后几位。

hashmap 开始默认长度是16,每次扩展或手动初始化都必须是2的幂函数,
hashmap使用长度为16的数组,为了使hashmap分布更加均匀

Hashmap 冲突解决方法
1.开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
2.再哈希法
3.链地址法
java7与8不同,7头插法,8尾插法
4.建立一个公共溢出区

hashmap 扩容方式:
触发条件
当hashmap中的元素个数超过数组大小*loadFactor,容量扩容为原来两倍, loadFactor为0.75
扩容方式
数组增加到原来两倍,遍历原Entry数组,把所有的Entry重新Hash到新数组。
JDK1.8使用红黑树这种数据结构来解决链表过长的问题(可以简单理解为用红黑树遍历比链表遍历速度快,时间复杂度低,不懂红黑树的可以去搜搜看),默认链表长度达到8就将链表树形化头插法在多线程中会产生死循环

ConcurrentHashMap
Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;
而ConcurrentHashMap中则是一次锁住一个桶。

5、拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

红黑树简介:
1、每个节点非红即黑
2、根节点总是黑色的
3、如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
4、每个叶子节点都是黑色的空节点(NIL节点)
5、从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

10、HashTable

数组 + 链表方式存储
默认容量: 11(质数 为宜)
put:
索引计算 : (key.hashCode() & 0x7FFFFFFF)% table.length
若在链表中找到了,则替换旧值,若未找到则继续
当总元素个数超过容量*加载因子时,扩容为原来 2 倍并重新散列。
将新元素加到链表头部
对修改 Hashtable 内部共享数据的方法添加了 synchronized,保证线程安全。

11、HashMap ,HashTable 区别

默认容量不同。扩容不同
线程安全性,HashTable 安全
效率不同 HashTable 要慢因为加锁

HashMap 与HashSet区别
数组易于快速读取(通过for循环),不便存储(数组长度有限制);链表易于存储,不易于快速读取。

哈希表的出现是为了解决链表访问不快速的弱点,哈希表也称散列表。

HashSet是通过HasMap来实现的,HashMap的输入参数有Key、Value两个组成,在实现HashSet的时候,保持HashMap的Value为常量,相当于在HashMap中只对Key对象进行处理。

HashMap的底层是一个数组结构,数组中的每一项对应了一个链表,这种结构称“链表散列”的数据结构,即数组和链表的结合体;也叫散列表、哈希表。

一、HahMap存储对象的过程如下

1、对HahMap的Key调用hashCode()方法,返回int值,即对应的hashCode;

2、把此hashCode作为哈希表的索引,查找哈希表的相应位置,若当前位置内容为NULL,则把hashMap的Key、Value包装成Entry数组,放入当前位置;

3、若当前位置内容不为空,则继续查找当前索引处存放的链表,利用equals方法,找到Key相同的Entry数组,则用当前Value去替换旧的Value;

4、若未找到与当前Key值相同的对象,则把当前位置的链表后移(Entry数组持有一个指向下一个元素的引用),把新的Entry数组放到链表表头;

二、HashSet存储对象的过程

往HashSet添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,

然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。

情况1: 如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。

情况2: 如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次

,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行添加。

在这里插入图片描述
set是一种关联式容器,其特性如下:
set以RBTree作为底层容器
所得元素的只有key没有value,value就是key
不允许出现键值重复
所有的元素都会被自动排序
不能通过迭代器来改变set的值,因为set的值就是键
map和set一样是关联式容器,它们的底层容器都是红黑树,区别就在于map的值不作为键,键和值是分开的。它的特性如下:
map以RBTree作为底层容器
所有元素都是键+值存在
不允许键重复
所有元素是通过键进行自动排序的
map的键是不能修改的,但是其键对应的值是可以修改的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值