简要实现java中的HashMap
每个key对应一个 int 型hashcode,通过散列函数将hashcode均匀的映射到散列数组table,常用的散列函数是取余操作,比如数组长度为16时散列函数可以是用hashcode对16取余,这样结果就在0~15之间,对应数组的每个下标。因为 int 型hashcode值范围必定远大于table数组长度,所以存在两个不同的hashcode映射到同一个数组位置,这里用到的处理冲突的方法是。实
·
实现的是JDK8以前的版本,也就是“散列+链表”结构。JDK8以后当链表长度大于8后会转换为树。
HashMap继承于Map接口,允许key、value值为null,key也可以相同(相同时值覆盖),线程不安全,效率高。底层的数据结构是“散列+链表”,如下图:
每个key对应一个 int 型hashcode,通过散列函数将hashcode均匀的映射到散列数组table,常用的散列函数是取余操作,比如数组长度为16时散列函数可以是用hashcode对16取余,这样结果就在0~15之间,对应数组的每个下标。因为 int 型hashcode值范围必定远大于table数组长度,所以存在两个不同的hashcode映射到同一个数组位置,这里用到的处理冲突的方法是链地址法:即把相同散列值的元素放在同一个单链表中,同时用数组存放各单链表的头结点。
下面是根据这一思想对HashMap中主要功能的简单实现。
class Node<K, V> {
int hash;
K key;
V value;
Node<K, V> next; // 指向单链表的下一个节点
}
class MyHashMap<K, V> {
Node<K, V>[] table; // 散列数组
int size; // 存放键值对的个数
@SuppressWarnings("unchecked")
public MyHashMap() {
table = new Node[16]; // 长度一般取2的整数次幂,默认16
}
// 根据hashcode返回散列值也就是散列数组下标
private int myHash(int v, int length) {
// 也可以return v&(length-1); v&(length-1)=v%(length),但与运算快于取余运算
return v % (length);
}
// 键值对存放
public void put(K key, V value) {
Node<K, V> newNode = new Node<>();
newNode.hash = myHash(key.hashCode(), table.length); // 获取散列值也就是散列数组下标
newNode.key = key;
newNode.value = value;
Node<K, V> curNode = table[newNode.hash]; // 获取散列数组下标位置元素
if (curNode == null) {
table[newNode.hash] = newNode; // 数组位置空直接放进去
size++;
} else {
Node<K, V> lastNode = null; // 表示链表中最后一个结点
boolean keyRepeat = false; // 标记key是否重复
while (curNode != null) {
if (curNode.key.equals(newNode.key)) {
keyRepeat = true;
curNode.value = newNode.value; // 重复时值覆盖
break;
} else {
lastNode = curNode; // 否则获取到单链表最后一个结点
curNode = curNode.next;
}
}
if (!keyRepeat) {
lastNode.next = newNode; // 如果键没有重复最后一个结点指向新结点
size++;
}
}
}
// 根据键获取值,没有这个键返回null
public V get(K key) {
int hash = myHash(key.hashCode(), table.length);
Node<K, V> curNode = table[hash];
V value = null;
while (curNode != null) {
if (curNode.key.equals(key)) {
value = curNode.value;
break;
}
curNode = curNode.next;
}
return value;
}
// 根据键删除键值对
public void remove(K key) {
int hash = myHash(key.hashCode(), table.length); // 获取hash值也就是散列数组地址
Node<K, V> curNode = table[hash]; // 获取散列数组该位置元素
if (curNode == null) {
System.out.println("没有找到该键!"); // 元素为空表示没有这样的key值
} else {
int ordNum = 1; // ordNum表示链表中key值的位置序号
Node<K, V> preNode = null; // 因为单链表所以需要保存key值处上一个位置的元素
while (curNode != null) {
if (curNode.key.equals(key)) {
break;
} else {
preNode = curNode;
curNode = curNode.next;
ordNum++;
}
}
if (curNode == null) { // 遍历到链表末尾没有找到
System.out.println("没有找到该键!");
} else if (ordNum == 1) { // 在链表头结点用第二个节点覆盖头结点
System.out.println("删除成功!删除元素是---" + table[hash].key + ":" + table[hash].value);
table[hash] = table[hash].next;
size--;
} else { // 存在且不在头结点
preNode.next = curNode.next; // 当前结点上一个元素指向当前结点下一个元素
System.out.println("删除成功!删除元素是---" + curNode.key + ":" + curNode.value);
size--;
curNode = null; // 释放空间
}
}
}
// 重写toString方法打印内容
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < table.length; i++) {
Node<K, V> curNode = table[i];
while (curNode != null) {
sb.append(curNode.key + ":" + curNode.value + ",");
curNode = curNode.next;
}
}
if (sb.charAt(sb.length() - 1) == ',') {
sb.setCharAt(sb.length() - 1, '}');
} else {
sb.append("}");
}
return sb.toString();
}
}
// 测试
public class TestMyHashMap {
public static void main(String[] args) {
MyHashMap<Integer, String> map = new MyHashMap<>();
map.put(10, "小王1");
System.out.println(map);
map.put(10, "小王2");
System.out.println(map);
map.put(53, "小张1");
map.put(69, "小张2");
map.put(85, "小张3");
System.out.println(map);
System.out.println(map.get(53));
map.remove(53);
System.out.println(map);
System.out.println("长度" + map.size);
}
}
测试结果
更多推荐
所有评论(0)