哈希表算是一种特殊的字典。它在实际的键值和存入的哈希值之间存在一层映射。如下例子:
上图中通过哈希函数将键值转换成哈希值, 然后再将哈希值指向具体的值。接着我们来构造 HashTable 类, 代码如下:
function HashTable() {this.items = {}}// 哈希算法function keyToHash(key) {let hash = 0for (let i = 0; i < key.length; i++) {hash += key.charCodeAt(i)}hash = hash % 37 // 为了避免 hash 的值过大return hash}HashTable.prototype.put = function(key, value) {const hash = keyToHash(key)this.items[hash] = value}HashTable.prototype.get = function(key) {return this.items[keyToHash(key)]}HashTable.prototype.remove = function(key) {delete(this.items[keyToHash(key)])}
跑如下测试用例:
var test = new HashTable()test.put('ab', 'ab@gmail.com')test.put('cd', 'cd@gmail.com')test.put('ef', 'ef@gmail.com')test.get('cd') // "cd@gmail.com"test.remove('cd')test.get('cd') // undefined
但是这样子实现的哈希表有一个问题, 比如进行如下调用就会产生冲突:
test.put('ab', 'ab@gmail.com')test.put('ba', 'ba@gmail.com') // ab 和 ba 的哈希值相同, 后者会把前者覆盖
接着我们来尝试解决该问题
顾名思义, 这个方法就是在每个哈希值上引人链表。如下图所示:
对之前 put、get、remove 方法做如下修改, 其中使用到的链表的代码参考之前的 链表
function HashTable() {this.items = {}}function keyToHash(key) {let hash = 0for (let i = 0; i < key.length; i++) {hash += key.charCodeAt(i)}hash = hash % 37return hash}// 存入链表的值function Node(key, value) {this.key = keythis.value = value}// 添加接口HashTable.prototype.put = function(key, value) {const hash = keyToHash(key)if (!this.items[hash]) {this.items[hash] = new LinkedList() // 这里将之前实现的链表拿来使用}let linkList = this.items[hash].getHead()let ifAppend = truewhile (linkList) { // 以下为 append 逻辑if (linkList.element.key === key) { // key 值重复逻辑linkList.element = new Node(key, value)ifAppend = falsebreak}linkList = linkList.next}if (ifAppend) {this.items[hash].append(new Node(key, value))}}HashTable.prototype.has = function (hash) {if (this.items.hasOwnProperty(hash)) {return true}return false}// 获取接口HashTable.prototype.get = function(key) {const hash = keyToHash(key)if (this.has(hash)) {let current = this.items[hash].getHead()while (current) {if (current.element.key === key) {return current.element.value}current = current.next}}return undefined}// 移除接口HashTable.prototype.remove = function(key) {const hash = keyToHash(key)if (this.has(hash)) {let current = this.items[hash].getHead()while (current) {if (current.element.key === key) {this.items[hash].remove(current.element)return true}current = current.next}return false}return false}
接着来测试下完成的哈希表, 测试用例如下:
var test = new HashTable()test.put('ab', 'ab@gmail.com')test.put('ba', 'ba~@gmail.com')test.put('ba', 'ba@gmail.com') // 验证重复字段test.put('cd', 'cd@gmail.com')test.get('ab') // ab@gmail.comtest.get('ba') // ba@gmail.comtest.get('cd') // cd@gmail.comtest.remove('ba')test.get('ba') // undefined
思想: 如果当前所要存储的 hash 值已存在于存储空间, 则判断存储空间里是否已存储 hash + 1, 若无则存储 hash + 1, 若有则判断存储空间里是否已存储 hash + 2, 依次类推。参考图如下:
接着进入代码实现环节, 同样是修改 put、get、remove 方法:
function HashTable() {this.items = {}}// 哈希算法function keyToHash(key) {let hash = 0for (let i = 0; i < key.length; i++) {hash += key.charCodeAt(i)}hash = hash % 37 // 为了避免 hash 的值过大return hash}// 后面会用之锁定function Node(key, value) {this.key = keythis.value = value}HashTable.prototype.put = function(key, value) {let hash = keyToHash(key)while (this.items[hash]) { // 当 this.items[index] 不存在时终止if (this.items[hash].key === key) { // 对已存在的 key 值进行覆盖break}hash++}this.items[hash] = new Node(key, value)}HashTable.prototype.has = function(hash) {if (this.items.hasOwnProperty(hash)) {return true}return false}HashTable.prototype.get = function(key) {let hash = keyToHash(key)if (this.items[hash]) {while (this.items[hash].key !== key) { // 找到存储的 indexhash++}return this.items[hash].value}return undefined}HashTable.prototype.remove = function(key) {let hash = keyToHash(key)if (this.has(hash)) {while (this.items[hash].key !== key) { // 找到存储的 indexhash++}this.items[hash] = undefinedreturn true}return false}
接着跑如下测试用例:
var test = new HashTable()test.put('ab', 'ab@gmail.com')test.put('ba', 'ba@gmail.com')test.put('ab', 'ab@gmail.com')test.get('ab') // 'ab@gmail.com'test.remove('ab')test.get('ab') // undefined
另外在本文实现的哈希函数中, 性能不是特别好(因为容易产生相同的哈希值), 给出一段更好的散列函数的实现, 数学知识的原理暂时不深究了
// 哈希算法function keyToHash(key) {let hash = 5381 // 取一个素数for (let i = 0; i < key.length; i++) {hash = hash * 33 + key.charCodeAt(i)}hash = hash % 1013 // 除以另外一个素数return hash}
笔者认为业务中, 哈希表的数据结构能应用需要加密处理的数据存储中。