HashMap
1.什么时候会使用HashMap?他有什么特点?
HashMap存储键值对,实现快速存取数据;允许null键/值;非同步;不保证有序(比如插入的顺序)。实现map接口。
2.HashMap的实现原理即内部数据结构?
底层使用哈希表(数组 + 链表)实现 jdk1.8及之前, 数组+链表+红黑树。jdk1.9
3.HashMap的put方法
hashMap.put(key,value);
(1)通过hash(key)计算出元素在数组中的存储位置
(2)如果key出现hash冲突,如何解决
当key出现hash冲突的时候,链表中的第一个元素都是后面最新添加进来的那个,之前的则被next变量引用着。,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
【1】对key的hashCode做hash操作,然后再计算在bucket中的index(1.5 HashMap的哈希函数);
【2】如果没碰撞直接放到bucket里;
【3】如果碰撞了,以链表的形式存在buckets后;
【4】如果节点已经存在就替换old value(保证key的唯一性)
【5】如果bucket满了(超过阈值,阈值=loadfactor*current capacity,load factor默认0.75),就要resize。
4. get()方法的工作原理?
通过hash(key)计算出元素在数组中的存储位置。如果产生碰撞,则利用key.equals()方法去遍历链表中查找对应的节点。
5.HashMap中hash函数怎么是是实现的?还有哪些 hash 的实现方式?
还有数字分析法、平方取中法、分段叠加法、 除留余数法、 伪随机数法。
6 HashMap 怎样解决冲突?
HashMap中处理冲突的方法实际就是链地址法,内部数据结构是数组+单链表。
抛开 HashMap,hash 冲突有那些解决办法?
开放定址法、链地址法、再哈希法。
7. 如果两个键的hashcode相同,你如何获取值对象?
通过hash(key)计算出元素在数组中的存储位置两个键的hashcode相同会产生碰撞,则利用key.equals()方法去链表或树(java1.8)中去查找对应的节点。
8. 针对 HashMap 中某个 Entry 链太长,查找的时间复杂度可能达到 O(n),怎么优化?
将链表转为红黑树,实现 O(logn) 时间复杂度内查找。JDK1.8 已经实现了。
9. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
扩容。这个过程也叫作rehashing,因为它重建内部数据结构,并调用hash方法找到新的bucket位置。
大致分两步:
1.扩容:容量扩充为原来的两倍(2 * table.length);
2.移动:对每个节点重新计算哈希值,重新计算每个元素在数组中的位置,将原来的元素移动到新的哈希表中。
补充:
loadFactor:加载因子。默认值DEFAULT_LOAD_FACTOR = 0.75f;
capacity:容量;
threshold:阈值=capacity*loadFactor。当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍(capacity*2);
size:HashMap的大小,它是HashMap保存的键值对的数量。
10 为什么String, Interger这样的类适合作为键?
String, Interger这样的类作为HashMap的键是再适合不过了,而且String最为常用。
因为String对象是不可变的,而且已经重写了equals()和hashCode()方法了。
1.不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。
因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
11.能否让HashMap同步?
HashMap可以通过下面的语句进行同步:Map m = Collections.synchronizeMap(hashMap);
12.HashMap为什么是线程不安全的
在hashmap做put操作的时候会调用到以上的方法。现在假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失
初始化桶大小,因为底层是数组,所以这是数组默认的大小。
桶最大值。
默认的负载因子(0.75)
table 真正存放数据的数组。
Map 存放数量的大小。
桶大小,可在初始化时显式指定。
负载因子,可在初始化时显式指定。
Entry 是 HashMap 中的一个内部类,从他的成员变量很容易看出:
key 就是写入时的键。
value 自然就是值。
开始的时候就提到 HashMap 是由数组和链表组成,所以这个 next 就是用于实现链表结构。
hash 存放的是当前 key 的 hashcode。
13.hashcode
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值
14.哈希函数
为此在建立一个哈希表之前需要解决两个主要问题:
⑴构造一个合适的哈希函数
均匀性 H(key)的值均匀分布在哈希表中;以提高地址计算的速度。 均匀的哈希函数可以减少冲突,但不能避免冲突
⑵冲突的处理
冲突:在哈希表中,不同的关键字值对应到同一个存储位置的现象。
经常使用的构造散列函数的方法
1. 直接寻址法:取keyword或keyword的某个线性函数值为散列地址。即H(key)=key或H(key) = a•key + b,当中a和b为常数(这样的散列函数叫做自身函数)
2. 除留余数法:取keyword被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅能够对keyword直接取模,也可在折叠、平方取中等运算之后取模。对p的选择非常重要,一般取素数或m,若p选的不好,easy产生同义词。
经常使用的解决hash冲突的方法
1.链地址法
链接地址法的思路是将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。
2.开放定址法
2.1线行探查法
线行探查法是开放定址法中最简单的冲突处理方法,它从发生冲突的单元起,依次判断下一个单元是否为空,当达到最后一个单元时,再从表首依次判断。直到碰到空闲的单元或者探查完全部单元为止。
2.2平方探查法即二次探测
平方探查法即是发生冲突时,用发生冲突的单元d[i], 加上 1²、 2²等。即d[i] + 1²,d[i] + 2², d[i] + 3²...直到找到空闲单元。
浙公网安备 33010602011771号