HashMap

HashMap

HashMap是hash表的一种实现,数据采用键值对的方式存储,
解决冲突采用的是链地址法,当有数据发生冲突时,将冲突的部分链接成链表。
HashMap是一种数据结构,可以快速的帮我们存取数据,在jdk1.7的时候采用的是数组加链表的形式,1.8之后采用的是数组加链表还有红黑树的形式,当链表个数大于8个时候就会变成红黑树,如果长度降到6之后又会变成链表,黑树的出现解决了因为链表过长导致查询速度变慢的问题,因为链表的查询时间复杂度是O(n),而红黑树的查询时间复杂度是O(logn)。

HashMap的put操作?

在这里插入图片描述
1 根据key计算hash值得到插入数组的索引index
2 判断该数组是否有值如果没值之间放入,如果有值判断key是否一样,如果一样覆盖,不一样判断数组后面的是链表还是红黑树
3如果是红黑树直接插入键值对,
4如果是链表,就判断链表的长度是否大于8了如果大于8了就变成红黑树,插入键值对,
5就开始遍历这个链表,如果找到相同的key值呢就覆盖掉这个key所对应的value方法,如果没有的话就插入
6 最后一步会判断是否需要扩容如果需要扩容就扩容两倍

HashMap的get操作?

在这里插入图片描述

HashMap和treeMap的区别

  1. HashMap是基于数组+链表+红黑树实现的 TreeMap是基于红黑树实现的
  2. HashMao可以允许键值对为NULL,TreeMap不允许
  3. HashMap存入元素不保证顺序,TreeMap在放入元素的时候实现了comparable接口按照排序后的顺序存入元素
  4. HashMap和TreeMap都不是线程安全的

HashMap和HashTable的区别

  1. HashMap不是线程安全的 HashTable是线程安全的
  2. HashMao可以允许键值对为NULL,HashTable不允许
  3. HashMap是基于数组+链表+红黑树实现的 HashTable是基于数组加链表实现的
  4. HashMap的初始大小为16 ,负载因子为 0.75.,是按照原来容量的两倍进行扩容的,扩容的结果一定是2的幂次倍扩容结果,并且每次扩容之后都呀重新计算数组元素的存放位置并重新插入,HashTable的初始大小是11 是按照原容量2倍加1进行扩容的。
  5. HashMap计算hash值有自己的hash方法,首先通过HashCode()算出Hash值,再把算出的hash值与右移16后异或,得到新的hash值;HashTable是通过HashCode()方法来得到hash值。

LinkedHashMap

LinkedHashMap是基于HashMap实现的,是一个有序的HashMap。LinkedHashMap提供的节点添加了两个属性before和after节点,用来生成双向循环列表的,这样每次在Map中添加值都会追加到链表的最后一位,这样就按照插入顺序生成了一个链表
LinkedHashMap实现有序key值的关键就是根据插入顺序另外维护了一个按照插入顺序作为标记的双向循环列表,这样在获取所有数据进行循环获取时获取到的数据就是有序的数据。

HashSet和HashMap的区别?

  1. HashMap实现了Map接口,HashSet实现的是Set接口
  2. HashMap存储的是键值对,HashSET存储的是对象
  3. HashSet的实现上是依赖于HashMap实现的,依靠HashMap的key来存储值,而value值默认为Object对象,所以HashSet不允许出现重复值

HashSet如何保证元素都是不重复的

add方法
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

HashSet的实现是依赖于HashMap实现的,HashSet的值都是存储在HashMap中的,HashSet的元素作为HashMap的key值,而hashmap的value值则默认为一个常量PRESENT 是一个空对象,所以说HashSet的key值不能重复

ConcurrentHashMap如何保证线程安全的?

ConcurrentHashMap在1.8中是通过CAS+synchronized来保证线程安全的,在put方法存入元素的时候通过key值得到数组的索引,如果没有Node的,就是用CAS插入元素,失败则无条件自旋直到插入成功。如果存在Node就是用Synchronized锁住该Node元素,链表或者红黑树的头结点,再执行插入操作。
在1.7是基于segment分段锁的方式

自旋是啥?

不断的尝试获取锁。

HashMap中为什么数组的长度为2的幂次方?

bucketIndex = indexFor(hash, table.length);

static int indexFor(int h, int length) {
     return h & (length-1);
}

HashMap的长度为2的幂次方的原因是为了减少Hash碰撞,尽量使Hash算法的结果均匀分布
主要是因为计算hash值的时候的散列性更好
HashMap的数组下标如何计算的
(数组的长度-1)和hash值进行按位与操作: i为数组对应位置的索引 n为当前数组的大小
i = (n - 1) & hash

假定HashMap的长度为默认的16,则n - 1为15,也就是二进制的01111,可以说,Hash算法最终得到的index结果完全取决于hashCode的最后几位。

HashMap的长度为10,则n-1为9,也就是二进制的1001
我们来试一个hashCode:1110时,通过Hash算法得到的最终的index是8
再比如说:1000得到的index也是8。
也就是说,即使我们把倒数第二、三位的0、1变换,得到的index仍旧是8,说明有些index结果出现的几率变大!

这样,显然不符合Hash算法均匀分布的要求。
长度16或其他2的幂次方,Length - 1的值的二进制所有的位均为1,这种情况下,Index的结果等于hashCode的最后几位。只要输入的hashCode本身符合均匀分布,Hash算法的结果就是均匀的。

HashMap的初始容量,加载因子,扩容增量是多少?如果加载因子变大变小会怎么样?

初始容量为16 加载因子是0.17 扩容增量是原容量的1倍
HashMap扩容是指元素个数(包括数组和链表+红黑树中)超过了16*0.75=12(容量×加载因子)之后开始扩容。
加载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了。反之,加载因子越小,填满的元素越少,冲突的机会减小,但空间浪费多了(因为需要经常扩容)。所以这是一个时间和空间的均衡。

为什么采用hashcode的高16位和低16位异或能降低hash碰撞?hash函数能不能直接用key的hashcode?

因为调用hashcode方法的时候会返回一个int类型的散列值,int的数据范围为**-2147483648~2147483647**,前后加起来大概40亿的映射空间,数据量太大,数组放不下,需要对其取于查操作,就很浪费。
右移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。
而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

HashMap的Hash方法

先通过HashCode方法得到一个Key的HashCode值,然后让这个hashcode值的高16位和低16位进行异或。之所以这样做呢是因为
1要尽可能降低hash碰撞,越分散越好;
2 算法一定要尽可能高效,因为这是高频操作, 因此采用位运算;

HashCode

HashCode()是用来获取哈希码的,也称为散列吗,是一个int类型的整数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值