HashMap是使用最频繁、最重要的数据结构之一,提供了字典操作功能,Insert、Search、Delete操作的效率都很高。本文将尝试用Java实现一个最简单的HashMap。因为简单,才容易看到HashMap的本真设计思想。
什么是Hash
依我的理解,Hash就是把一个对象转化为一个正整数的过程。相同的对象产生相同的Hash Code,但不同的对象的Hash Code具有随机的特点,不同的Hash Code均匀分布。不同的对象也可能Hash Code相同,产生碰撞。
为什么Hash
Map存储的是键值对,存储的方式一般通过数组来完成。假设Key的可能集合为S,如果不Hash,那么要创建一个size大于等于S.length的数组来存储键,否则某些Key可能找不到槽位来放数据。当S很大,而真正使用到的数据又相对较小的时候,会造成空间的浪费。把S映射到一个较小的集合R,Hash正好用来做这样的Mapping,只不过得到的整数要处理一下,大小控制在R的范围之内。
具体实现
主要包括Insert(新增),Put(新增或者修改),Search(查询),Delete(删除)四个操作。Hash的过程利用了Object的hashcode(),也参考了算法导论的乘法Hash算法。解决碰撞使用了链表法,注意Node的next属性。
public class HashMap<K, V> {
private static int DEFAULT_CAPACITY = 16;
private static double A = (Math.pow(5, 0.5) - 1) / 2;
private int capacity;
private int size = 0;
private Node<K, V>[] buckets;
public HashMap() {
this(DEFAULT_CAPACITY);
}
@SuppressWarnings("unchecked")
public HashMap(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException(
"capacity can not be negative or zero");
}
// 保证 capacity 是2的n次方
int temp = 1;
while (temp < capacity) {
temp <<= 2;
}
this.capacity = temp;
buckets = new Node[this.capacity];
}
public void insert(K key, V value) {
if (key == null) {
throw new IllegalArgumentException("key can not be null");
}
int position = index(key);
Node<K, V> node = new Node<K, V>(key, value);
if (buckets[position] != null) {
node.setNext(buckets[position]);
}
buckets[position] = node;
size++;
}
public void put(K key, V value) {
if (key == null) {
throw new IllegalArgumentException("key can not be null");
}
int position = index(key);
Node<K, V> node = buckets[position];
while (node != null) {
if (node.key.equals(key)) {
node.value = value;
return;
}
node = node.next;
}
Node<K, V> newNode = new Node<K, V>(key, value);
if (buckets[position] != null) {
newNode.setNext(buckets[position]);
}
buckets[position] = newNode;
size++;
}
public void delete(K key) {
if (key == null) {
throw new IllegalArgumentException("key can not be null");
}
int position = index(key);
Node<K, V> node = buckets[position];
if (node == null) {
return;
}
if (node.key.equals(key)) {
buckets[position] = node.next;
size--;
}
while (node.next != null) {
if (node.next.key.equals(key)) {
node.next = node.next.next;
size--;
break;
}
node = node.next;
}
}
public V search(K key) {
if (key == null) {
throw new IllegalArgumentException("key can not be null");
}
int position = index(key);
Node<K, V> node = buckets[position];
while (node != null) {
if (node.key.equals(key)) {
return node.value;
}
node = node.next;
}
return null;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("{");
for (int i = 0; i < capacity; i++) {
Node<K, V> node = buckets[i];
while (node != null) {
buffer.append(node.key + ":" + node.value + ", ");
node = node.next;
}
}
if (buffer.length() > 1) {
buffer.delete(buffer.length() - 2, buffer.length());
}
buffer.append("}");
return buffer.toString();
}
private int index(K key) {
int hashCode = key.hashCode();
double temp = hashCode * A;
double digit = temp - Math.floor(temp);
return (int) Math.floor(digit * capacity);
}
static class Node<K, V> {
private final K key;
private V value;
private Node<K, V> next;
public Node(K key, V value) {
this.key = key;
this.value = value;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public Node<K, V> getNext() {
return next;
}
public void setNext(Node<K, V> next) {
this.next = next;
}
public K getKey() {
return key;
}
}
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("001", "James");
map.put("002", "Antony");
map.put("003", "Bosh");
map.put("004", "Wade");
map.put("004", "WestBrook");
System.out.println(map);
System.out.println(map.size());
System.out.println(map.search("004"));
}
}