散列表 - 手动实现 哈希表(HashMap)
1.1 哈希表的介绍
- 散列表也叫作哈希表(hash table),这种数据结构提供了键(Key)和值(Value)的映射关系。只要给出一个Key,就可以高效查找到它所匹配的Value,时间复杂度接近于O(1)
- 存储原理
- 哈希函数
- 散列表在本质上也是一个数组
- 散列表的Key则是以字符串类型为主的
- 通过hash函数把Key和数组下标进行转换
- 作用是把任意长度的输入通过散列算法转换成固定类型、固定长度的散列值,就像数组生成了对应的整数下标,便于寻找对应hash关系的元素
操作
-
写操作(put)
- 写操作就是在散列表中插入新的键值对(在JDK中叫作Entry或Node)
- 第1步,通过哈希函数,把Key转化成数组下标
- 第2步,如果数组下标对应的位置没有元素,就把这个Entry填充到数组下标的位置
-
Hash冲突(碰撞)
-
读操作(get)
- 读操作就是通过给定的Key,在散列表中查找对应的Value
- 第1步,通过哈希函数,把Key转化成数组下标
- 第2步,找到数组下标所对应的元素,如果key不正确,说明产生了hash冲突,则顺着头节点遍历该单链表,再根据key即可取值
-
Hash扩容(resize)
1.2 手动实现哈希表(HashMap)
package com.lagou.hashMap.entity;
public class MyHashMap {
public class MyLink{
private String key;
private String value;
private MyLink link;
public MyLink(String key, String value){
this.key = key;
this.value = value;
this.link = null;
}
public MyLink(String key, String value, MyLink node){
this.key = key;
this.value = value;
this.link = node;
}
}
public class ListNode{
private MyLink head;
public void addNode(String key, String value){
if (head == null){
return;
}
MyLink newLink = new MyLink(key, value);
MyLink temp = head;
while (true){
if (temp.key.equals(key)){
temp.value = value;
}
if (temp.link == null){
break;
}
temp = temp.link;
}
temp.link = newLink;
}
public String getNodeValue(String key){
String result = "null";
if (head == null){
return result;
}
MyLink temp = head;
while (true){
if (temp.key.equals(key)){
result = temp.value;
break;
}
if (temp.link == null){
break;
}
temp = temp.link;
}
return result;
}
}
private ListNode[] myHashMap = null;
private static final int HASHMAP_LENGTH = 8;
private int length = 0;
private static final float EXPANSION_FACTOR = 0.75F;
public MyHashMap(){
this.myHashMap = new ListNode[HASHMAP_LENGTH];
}
public MyHashMap(int len){
this.myHashMap = new ListNode[len];
}
public void resize(){
MyHashMap newMyHashMap = new MyHashMap(myHashMap.length * 2);
for (int i = 0; i < this.myHashMap.length; i++){
ListNode listNode = this.myHashMap[i];
if (listNode == null){
continue;
} else {
MyLink temp = listNode.head;
newMyHashMap.put(temp.key, temp.value);
while (true){
if (temp.link == null){
if(temp != listNode.head){
newMyHashMap.put(temp.key, temp.value);
}
break;
} else {
temp = temp.link;
newMyHashMap.put(temp.key, temp.value);
}
}
}
}
myHashMap = newMyHashMap.myHashMap;
}
public void put(String key, String value){
if (length >= myHashMap.length * EXPANSION_FACTOR){
length = 0;
MyHashMap newMyHashMap = new MyHashMap(myHashMap.length * 2);
for (int i = 0; i < myHashMap.length; i++){
ListNode listNode = myHashMap[i];
if (listNode == null){
continue;
} else {
MyLink temp = listNode.head;
newMyHashMap.put(temp.key, temp.value);
while (true){
if (temp.link == null){
if(temp != listNode.head){
newMyHashMap.put(temp.key, temp.value);
}
break;
} else {
temp = temp.link;
newMyHashMap.put(temp.key, temp.value);
}
}
}
}
myHashMap = newMyHashMap.myHashMap;
System.out.println("HashMap 进行扩容成功");
}
int index = Math.abs(key.hashCode()) % myHashMap.length;
ListNode arrayListNode = myHashMap[index];
if (arrayListNode == null){
ListNode newListNode = new ListNode();
MyLink headLink = new MyLink(key, value);
newListNode.head = headLink;
myHashMap[index] = newListNode;
length++;
} else {
arrayListNode.addNode(key, value);
}
}
public String get(String key){
String result = "null";
int index = Math.abs(key.hashCode()) % myHashMap.length;
ListNode arrayListNode = myHashMap[index];
if (arrayListNode.head == null){
return result;
} else {
MyLink temp = arrayListNode.head.link;
while (true){
if (temp.value.equals(key)){
result = temp.value;
break;
}
if (temp.link == null){
break;
}
temp = temp.link;
}
}
return result;
}
public String select(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("【HashMap】\n");
for (int i = 0; i < myHashMap.length; i++){
ListNode listNode = myHashMap[i];
if (listNode == null){
stringBuilder.append("【哈希码值:null】\n");
} else {
stringBuilder.append("【哈希码值:" + listNode.head.key.hashCode() + "】");
MyLink temp = listNode.head;
while (true){
if (temp.link == null){
stringBuilder.append(temp.value + "\n");
break;
} else {
stringBuilder.append(temp.value + " => ");
temp = temp.link;
}
}
}
}
return stringBuilder.toString();
}
}
1.3 进行测试
package com.lagou.hashMap.test;
import com.lagou.hashMap.entity.MyHashMap;
public class MyHashMapTest {
public static void main(String[] args) {
MyHashMap myHashMap = new MyHashMap(10);
for (int i = 1; i <= 20; i++){
myHashMap.put(i + "", i + "");
}
System.out.println(myHashMap.select());
}
}
1.4 哈希表的总结
-
时间复杂度
- 写操作: O(1) + O(m) = O(m) ,m为单链元素个数
- 读操作:O(1) + O(m) ,m为单链元素个数
- Hash冲突写单链表:O(m)
- Hash扩容:O(n) ,n是数组元素个数 rehash
- Hash冲突读单链表:O(m) ,m为单链元素个数
-
优缺点
- 优点:读写快
- 缺点:哈希表中的元素是没有被排序的、Hash冲突、扩容 重新计算
-
应用
-
HashMap
- JDK1.7中HashMap使用一个table数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同,那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表,在极端情况下比如说所有key的hashcode都相同,将会导致这个链表会很长,那么put/get操作需要遍历整个链表,那么最差情况下时间复杂度变为O(n)
- 扩容死链
- 针对JDK1.7中的这个性能缺陷,JDK1.8中的table数组中可能存放的是链表结构,也可能存放的是红黑树结构,如果链表中节点数量不超过8个则使用链表存储,超过8个会调用treeifyBin函数,将链表转换为红黑树。那么即使所有key的hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(logn)的开销
|