HashMap

HashMap的内部可以看做数组+链表+红黑树的复合结构。数组被分为一个个的桶(bucket)。哈希值决定了键值对在数组中的寻址。具有相同哈希值的键值对会组成链表。需要注意的是当链表长度超过阈值(默认是8)的时候会触发树化,链表会变成树形结构。

把握HashMap的原理需要关注4个方法:hash、put、get、resize。
  • hash方法。 将 key 的 hashCode 值的高位数据移位到低位进行异或运算。这么做的原因是有些 key 的 hashCode 值的差异集中在高位,而哈希寻址是忽略容量以上高位的,这种做法可以有效避免哈希冲突。
  • put 方法。put 方法主要有以下几个步骤:通过 hash 方法获取 hash 值,根据 hash 值寻址。如果未发生碰撞,直接放到桶中。如果发生碰撞,则以链表形式放在桶后。当链表长度大于阈值后会触发树化,将链表转换为红黑树。如果数组长度达到阈值,会调用 resize 方法扩展容量。
  • get方法。get 方法主要有以下几个步骤:通过 hash 方法获取 hash 值,根据 hash 值寻址。如果与寻址到桶的 key 相等,直接返回对应的 value。如果发生冲突,分两种情况。如果是树,则调用 getTreeNode 获取 value;如果是链表则通过循环遍历查找对应的 value。
  • resize 方法。resize 做了两件事:将原数组扩展为原来的 2 倍重新计算 index 索引值,将原节点重新放到新的数组中。这一步可以将原先冲突的节点分散到新的桶中。
HashMap HashTable区别
  • HashTable方法同步,方法用synchronized修饰,多线程场合

  • HashTable不允许null值

  • 遍历方式,HashTable使用Enumeration遍历,HashMap使用Iterator遍历

  • HshTable增加为old*2 + 1,HashMap默认16,增加为2的指数倍

  • HashMap是fail-fast的,HashTable提供对key的Enumeration进行遍历,不支持fail-fast

  • HashTable线程安全,但是需要获得对象锁,一般使用CurrentHashMap

  • HashTable竞争同一把锁

HashSet

内部是hashmap

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

存储唯一元素并允许空值

HashSet如何保持唯一性?

当我们将一个对象放入一个HashSet时,它使用该对象的hashcode值来确定一个元素是否已经在该集合中

每个散列码值对应于某个块位置,该块位置可以包含计算出的散列值相同的各种元素。但是具有相同hashCode的两个对象可能不相等。

因此,将使用equals()方法比较同一存储桶中的对象。

Java中的set是一个不包含重复元素的集合,确切地说,是不包含e1.equals(e2)的元素对。Set中允许添加null。Set不能保证集合里元素的顺序。

在往set中添加元素时,如果指定元素不存在,则添加成功。也就是说,如果set中不存在(enull ? e1null : e.queals(e1))的元素e1,则e1能添加到set中。

看源码我们知道,HashSet添加的元素是存放在HashMap的key位置上,而value取了默认常量PRESENT,是一个空对象

HashMap死循环问题
https://www.jianshu.com/p/1e9cf0ac07f4

HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get一个不存在的值时会出现死循环,引起CPU的100%问题,所以一定要避免在并发环境下使用HashMap

HashMap和ConcurrentHashMap

由于HashMap是线程不同步的,虽然处理数据的效率高,但是在多线程的情况下存在着安全问题,因此设计了CurrentHashMap来解决多线程安全问题。

在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,并且该类里面维护了一个 HashEntry<K,V>[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。

在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。

ConcurrentHashMap
抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性

HashMap如果我想要让自己的Object作为Key应该怎么办
  1. 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
  2. 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性(Java建议重写equal方法的时候需重写hashcode的方法)
TreeMap

适合对一个key集合进行有序的遍历

插入、删除、定位hashmap是最好的选择

开放定址法和链地址法

https://blog.csdn.net/gdhuyufei/article/details/37964875

开放定址法:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。

链地址法:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。

HashMap
https://blog.csdn.net/weixin_44339238/article/details/108733505

红黑树

平衡的二叉查找树
节点是红色或者是黑色
根节点是黑色
每个叶子的节点都是黑色的空节点(NULL)
每个红色节点的两个子节点都是黑色的
从任意节点到其每个叶子的所有路径都包含相同的黑色节点
插入时会涉及到变色和旋转

hashmap 原理,红黑树是什么?
1.7 数组+链表,链表过长时,会导致查询效率退化
1.8 数组+链表+红黑树,当链表长度大于8转为红黑树
HashMap 的默认初始大小为 16,初始化大小必须为 2 的幂,最大大小为 2 的 30 次方。数组中存储的链表节点 Entry 类实现于 Map.Entry 接口,它实现了对节点的通用操作。HashMap 的阈值默认为 “容量 * 0.75f”,当存储节点数量超过该值,则对 map 进行扩容处理。
线程不安全的容器,解决并发问题使用ConcurrentHashMap(高效)或者是Collections.synchronizedMap().Collections.synchronizedMap()其实就是每个方法加一个synchronize,其实和HashTable 差不多.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值