1、HashMap结构:
Java7 : 数组 + 链表
Java8 : 数组 + 链表 或 红黑树 (链表超过8则转为红黑树,小于6则变会链表) >> 加快查询 JDK1.7和JDK1.8的区别是基于数组上的链表是否转换成红黑树,本篇文章着重讲JDK1.7的HashMap实现。
2、存取操作:
1)存储元素过程:
写操作就是在散列表中插入新的键值对(在JDK中叫作Entry或Node) 在Entry中保存key和值,以及next指针
Entry{
int key;
Object value;
Entry next;
}
第1步,通过哈希函数,把Key转化成数组下标
index = HashCode (Key) % Array.length;
int index=Math.abs("Hello".hashCode())%10;
第2步,如果数组下标对应的位置没有元素,就把这个Entry填充到数组下标的位置。 假设key的hashcode为13,数组长度为 8,则要存储在数组索引为5的位置(13 % 8=5) Hash冲突(碰撞) 由于数组的长度是有限的,当插入的Entry越来越多时,不同的Key通过哈希函数获得的下标有可能是相同的,这种情况,就叫作哈希冲突。
链表法:数组的每一个元素不仅是一个Entry对象,还是一个链表的头节点。每一个Entry对象通过next指针 指向它的下一个Entry节点。当新来的Entry映射到与之冲突的数组位置时,只需要插入到对应的链 表中即可,默认next指向null
2)读取元素过程:
读操作就是通过给定的Key,在hash表中查找对应的Value 第1步,通过哈希函数,把Key转化成数组下标。 第2步,找到数组下标所对应的元素,如果key不正确,说明产生了hash冲突, 则顺着头节点遍历该单链表,再根据key即可取值 Hash扩容(resize) Hash表是基于数组实现的,所以散列表需要扩容 当经过多次元素插入,散列表达到一定饱和度时,Key映射位置发生冲突的概率会逐渐提高。这样 一来,大量元素拥挤在相同的数组下标位置,形成很长的链表,对后续插入操作和查询操作的性能 都有很大影响 影响扩容的因素有两个 Capacity:HashMap的容量长度 LoadFactor:HashMap的负载因子(阈值),默认值为0.75f 当HashMap.Size >= Capacity×LoadFactor时,需要进行扩容
扩容的步骤:
- 扩容,创建一个新的Entry空数组,长度是原数组的2倍
- 重新Hash,遍历原Entry数组,把所有的Entry重新Hash到新数组中
3、代码实现
1)Node.java
public class Node {
String key;
String value;
Node next;
public Node(String key, String value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
说明:作为存储数据的数据结构,类似于上图数组中的Entry
2)ListNode.java
public class ListNode {
Node head;
public void addNode(String key, String value) {
if (head == null) return;
Node node = new Node(key, value, null);
Node tmp = head;
while (true) {
if(key.equals(tmp.key)){
tmp.value=value;
}
if(tmp.next==null){
break;
}
tmp=tmp.next;
}
tmp.next=node;
}
public String getVal(String key) {
if (head == null) return null;
if (head.next == null) {
return head.value;
}
else {
Node tmp = head;
while (tmp != null) {
if (key.equals(tmp.key)) {
return tmp.value;
}
tmp = tmp.next;
}
return null;
}
}
}
说明:以上代码,实现了在链表中添加和查询元素Node, 添加操作,相当于在链表末尾进行添加
3)MyHashMap.java
public class MyHashMap {
ListNode[] map=new ListNode[8];
int size;
public void put(String key,String value){
if(size>=map.length*0.75){
System.out.println("map需要扩容");
return;
}
int index=Math.abs(key.hashCode())%map.length;
ListNode ln=map[index];
if(ln==null){
ListNode lnNew=new ListNode();
Node head=new Node(key,value,null);
lnNew.head=head;
map[index]=lnNew;
size++;
}
else {
ln.addNode(key,value);
}
}
public String get(String key){
int index=Math.abs(key.hashCode())%map.length;
ListNode ln=map[index];
if(ln==null)
return null;
return ln.getVal(key);
}
public static void main(String[] args) {
MyHashMap hashMap=new MyHashMap();
hashMap.put("m3","cccccc");
hashMap.put("c1","kkkkkk");
hashMap.put("c1","mmmmmmm");
System.out.println(hashMap.get("c1"));
}
}
说明:当产生Hash碰撞的时候,就会引用到单链表结构 ListNode了
测试
|