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"));
- }
- }