首页 > 编程知识 正文

千字学会 HashMap 数据结构

时间:2023-05-04 05:34:36 阅读:129406 作者:1159

混洗地图

HashMap由基于哈希表的map界面实现,用于存储“关键帧-值对”(key-value )映射。 允许使用空值和空键。 hashmap类与hashtable大致相同,只是允许异步和使用空位置。 因为是基于混列表实现的,所以并不保证映射顺序,特别是其顺序是永久不变的。 因为每次扩展都会重新计算hash。

迭代计算视图所需的时间与hashmap实例的“容量”(桶数)及其大小(键值和映射关系的数量)成比例。所以,如果迭代性能很重要,就不能把初始容量设置的太高或者将加载因子设置的太低。

HashMap中有两个参数影响其性能:初始容量和加载因子

容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 加载因子是哈希表在容量自动增加之前能达到多少的度量。 如果哈希表的数量超过当前加载系数与当前容量的乘积,则必须对哈希表执行rehash操作,即重建内部数据结构,使哈希表的桶数约为两倍。

加载因子

加载因子也称为扩展因子,判断何时扩展。 假设负载因子为0.75,混列映射的初始容量为16,则当混列映射具有16 * 0.75=12个容量时,混列映射扩展。 加载因子越大,容量扩大的发生频率越低,占用空间越小,但发生混列冲突的概率越高,对元素的操作时间增加,执行效率降低; 如果加载系数太小,表中的数据将过于稀疏,许多空间还没有使用就开始扩大容量。 空间会产生很大的浪费。 另外,由于默认情况下容量为2的平方,因此如果加载因子为0.75,则容量与加载因子的乘积为整数。 因此,系统的默认加载因子取0.5 -1之间的0.75。

哈希冲突散列冲突是指散列值冲突,其中多个key对象在put时计算散列值并且所得散列值相同。

计算的时候用一个数进行模运算。 取馀数意味着存储数组数据的下标,但可能存在馀数相同的内容。 这样一来,无法存储相同剩馀的数据,因此会发生混列冲突。

解决hash冲突的三种方法

-open-address :查找以下空数组下标并存储碰撞元素:

-再散列法(两次醉熏的太阳) :这里用不同的散列算法重新计算一次。

-链地址法(拉链法) hashmap使用按链表存储所有冲突元素的方法。 碰撞后的时间复杂度为o(1n ) n是碰撞的要素的个数。

两个关键方法:put和get

HashMap声明继承Map、Cloneable、Serializable接口和AbstractMap类,内部的迭代器主要是其内部类HashIterator和其他几个

publicobjectput (对象密钥,对象值) {

//这将判断为密钥值足够空,如果为空,则将static Object作为密钥值返回。 因此,hashmap允许空密钥的空值

objectk=掩码空值(key;

inthash=hash(k;

intI=indexfor(hash,table.length );

HashMap底层原理

HashMap在JDK1.8之前的实现方式数组+链表,

但是,在JDK1.8之后,在HashMap上进行基础优化,改为在数组+链表或者数值+红黑树上实现,主要目的是提高检索效率

当链表元素超过8个时,Jdk8数组+链表或者数组+红黑树实现将链表转换为红黑树,当红黑树节点小于或等于6时,实现简并为链表。

new HashMap ) )如果尚未在:下创建数组,则首次调用put ) )方法时,将在下面创建长度为16的数组。 位于jdk8下的数组是Node[] ()而不是Entry[] () ),它将数组的容量大小乘以负载系数以获得值,并在数组中存储的元素数超过此值时调用rehash方法。称为扩展的术语是扩展扩展操作非常消耗性能,因为所有原始数据都需要重新计算哈希代码值并将其重新分配给新数组。 默认负载系数大小为0.75,数组大小为16。 也就是说,默认情况下,如果HashMap的元素数超过16*0.75=12,则将数组的大小扩展为2*16=32,即两倍。

我们Java中的每个对象都有hashcode,hash算法通过hashcode与自己向右移位16

异或运算。这样做是为了计算出来的hash值足够随机,足够分散,还有产生的数组下标足够随机。

不同的对象算出来的数组下标是相同的这样就会产生hash冲突,当单线链表达到一定长度后效率会非常低。

在链表长度大于8的时候,将链表就会变成红黑树,提高查询的效率。

map.put(k,v)实现原理

(1)首先将k,v封装到Node对象当中(节点)。

(2)先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。

(3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

map.get(k)实现原理

(1)、先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。

(2)、在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

Hashmap和hashtable ConcurrentHashMap区别

区别对比一(HashMap 和 HashTable 区别):

1、HashMap 是非线程安全的,HashTable 是线程安全的。

2、HashMap 的键和值都允许有 null 值存在,而 HashTable 则不行。

3、因为线程安全的问题,HashMap 效率比 HashTable 的要高。

4、Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更适合于单线

程环境,而 Hashtable 适合于多线程环境。一般现在不建议用 HashTable, ①

是 HashTable 是遗留类,内部实现很多没优化和冗余。②即使在多线程环境下,

现在也有同步的 ConcurrentHashMap 替代,没有必要因为是多线程而用

HashTable。

区别对比二(HashTable 和 ConcurrentHashMap 区别):

HashTable 使用的是 Synchronized 关键字修饰,ConcurrentHashMap 是JDK1.7使用了锁分段技术来保证线程安全的。JDK1.8ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。

synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。