本文主要介绍手写JavaScript版HashMap,侧重于HashMap数据结构的实现,并实现其基本api,保证是一个可用的HashMap。
同时也由于时间关系和语言翻译的出入,对于内存自增长这块没有体现,同时hash算法也采用的简洁版,后续有空再优化。
一、HashMap的重要性
首先,我们为什么要研究HashMap?
HashMap是Java中极其重要的数据结构,这种结构我们称之为散列链表。即元素是单链链表的数组。
1,对于数据库,如果存在千万条数据,我们的增删查改每次都要遍历一整个数据库,这将是灾难性的。对此我们可以用HashMap这种数据结构做索引优化
2,微信通讯录的设计,右侧有个abcd···序列表,可以提高我们找好友的效率,这也是HashMap数据结构的一个体现
二、jdk HashMap源码分析
查看jdk源码,我们看出HashMap的继承关系:
LinkedHashMap -> HashMap -> AbstractMap -> Map
其数据结构:
Object[entry<Key,Value>>
java版用法。可以看出主要是对key value数据结构的一个保存
同时提供api
size(大小)
isEmpty(判空)
containsKey(检查key)
containsValue(检查value)
get(查找数据)
put(存数据)
remove(删除数据)
putAll(存多个数据)
clear(清空)
等方法
其存储逻辑主要依赖于hash算法,根据key把数据存到对应数组的链表中去,以分组的方法提高运算效率
三、JavaScript版HashMap实现
3.1、定义数据结构
首先我们定义一个[Node,Node]这样的数据结构。
对于链表节点我们我们需要一个key、value,另外添加next指针。
class Node {
constructor(key, value, next) {
this.key = key;
this.value = value;
this.next = next;
}
}
对于数组部分,我们定义一个table数组,并初始化结点,同时定义一个数组个数hashMapSize
class HashMap {
constructor() {
this.table = [];
this.hashMapSize = 0;
for (let index = 0; index < DEFAULT_INITIAL_CAPACITY; index++) {
this.table.push(null);
}
}
}
3.2、put存数据
对于put数据,首先我们需要查找存放到哪个数组
这里我们首先对key进行hash,并使用位运算或者取余运算映射到0~this.table.length的数组中去
const index = this.hash(key) & (this.table.length - 1);
// const index = this.hash(key) % this.table.length;
对于存数据,我们分两种情况,情况一,如果该数据在HashMap中已经有了,我们就修改value即可。
这里链表的遍历是依赖于next指针,直到为空
for (let headNode = this.table[index]; headNode != null; headNode = headNode.next) {
if (this.hash(headNode.key) === this.hash(key) && headNode.key === key) {
const oldValue = headNode.value;
headNode.value = value;
return oldValue;
}
}
情况二:如果没有则直接存到该链表的数据中,并且数据size加一。
存的操作:新增一个节点next指向原来的头节点,然后新节点上则作为头节点
this.table[index] = new Node(key, value, this.table[index])
this.hashMapSize++;
对于其他操作则比较简单,这里不再赘述,看下源码即可
四、HashMap源码
class Node {
constructor(key, value, next) {
this.key = key;
this.value = value;
this.next = next;
}
}
// The default initial capacity - MUST be a power of two.
const DEFAULT_INITIAL_CAPACITY = 1 << 4;
class HashMap {
constructor() {
this.clear();
}
// 大小
size() {
return this.hashMapSize;
}
// 判空
isEmpty() {
return this.hashMapSize === 0;
}
// 包含key
containsKey(key) {
return this.get(key) != null;
}
// 包含value
containsValue(value) {
for (let index = 0; index < DEFAULT_INITIAL_CAPACITY; index++) {
for (let headNode = this.table[index]; headNode != null; headNode = headNode.next) {
if (headNode.value === value) {
return true;
}
}
}
return false;
}
// 根据key获取value
get(key) {
const index = this.hash(key) & (this.table.length - 1);
for (let headNode = this.table[index]; headNode != null; headNode = headNode.next) {
if (this.hash(headNode.key) === this.hash(key) && headNode.key === key) {
return headNode.value;
}
}
return null;
}
// 存键值对
put(key, value) {
const index = this.hash(key) & (this.table.length - 1);
// const index = this.hash(key) % this.table.length;
for (let headNode = this.table[index]; headNode != null; headNode = headNode.next) {
if (this.hash(headNode.key) === this.hash(key) && headNode.key === key) {
const oldValue = headNode.value;
headNode.value = value;
return oldValue;
}
}
this.table[index] = new Node(key, value, this.table[index])
this.hashMapSize++;
return null;
}
// 删除
remove(key) {
const index = this.hash(key) & (this.table.length - 1);
let headNode = this.table[index];
if (headNode != null) {
for (; headNode.next != null; headNode = headNode.next) {
if (this.hash(headNode.next.key) === this.hash(key) && headNode.next.key === key) {
headNode.next = headNode.next.next;
}
}
}
return null;
}
// 存map
putAll(map) {
for (let index = 0; index < map.size(); index++) {
for (let headNode = map[index]; headNode != null; headNode = headNode.next) {
this.put(headNode.key, headNode.value);
}
}
}
// 清空
clear() {
this.table = [];
this.hashMapSize = 0;
for (let index = 0; index < DEFAULT_INITIAL_CAPACITY; index++) {
this.table.push(null);
}
}
// 打印所有数据
toString() {
let str = '{';
for (let index = 0; index < DEFAULT_INITIAL_CAPACITY; index++) {
for (let headNode = this.table[index]; headNode != null; headNode = headNode.next) {
if (str.length !== 1) {
str += ',';
}
str += (headNode.key + '=' + headNode.value);
}
}
console.log(str + '}');
}
hash(str){
var hashCode=0
for(var i=0;i<str.length;i++){
hashCode=37* hashCode + str.charCodeAt(i) //获取编码
}
return hashCode
}
}
五、验证结果
我们做存的操作,并打印出结果与数据结构:
const hashMap = new HashMap();
hashMap.put("张三", "18");
hashMap.put("李四", "19");
hashMap.put("王五", "20");
hashMap.put("赵六", "21");
hashMap.toString();
console.log(hashMap)
console.log(hashMap.get("张三"))
这里验证正确,并且可以看到数据已经分散到各个组里面去了。