如果让我们自己来设计HashMap,应该怎么做呢?数组。用数组存储节点,每个节点有key和value信息。那首先我们得要有节点存储key,value。
节点设计
在这个Node类中,有三个属性,hash值、key、value值。
1 class Node<K,V> { 2 final int hash; //hash值 3 final K key;//key 4 V value;//value 5 //构造函数 6 Node(int hash, K key, V value, Node<K,V> next) { 7 this.hash = hash; 8 this.key = key; 9 this.value = value; 10 this.next = next; 11 } 12 13 public final K getKey() { return key; } 14 public final V getValue() { return value; } 15 public final String toString() { return key + "=" + value; } 16 17 public final boolean equals(Object o) { 18 if (o == this) 19 return true; 20 if (Objects.equals(key, o.getKey()) && 21 Objects.equals(value, o.getValue())) 22 return true; 23 24 return false; 25 } 26 }
注意:这里的构造函数中多了个next,大家忽略即可。奇怪,添加了这个代码块,怎么编辑的时候删除不了。哎。。。
初始化数组
定义一个容量为100的数组,用来存储node节点。 DEFAULT_INITIAL_CAPACITY ,它必须是必须是2的幂,在JDK提供的HashMap它的值为1 << 4,也就是16,这里直接填16不可以吗?为啥一定要这样写?
1 static final int DEFAULT_INITIAL_CAPACITY=100; 2 transient Node<K,V>[] table; 3 /** 4 * 构造函数 5 */ 6 public SimpleHashMap() 7 { 8 table=(Node<K,V>[])new Node[DEFAULT_INITIAL_CAPACITY]; 9 }
增加操作-put
对一个容量为100的数组中添加元素其实很简单,具体步骤如下:
1)对key值进行hash,获取hash值。
2)通过上面获取的hash值、key、value值,创建node节点。
3)通过hash值和数组长度获取对应的下标。
4)对获取的下标赋值。
注意:这里对key进行hash调用的hash函数,具体我也不明白为啥要这样设计?
1 static final int hash(Object key) { 2 int h; 3 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 4 }
另外,根据hash值和容量(数组长度)获取对应的下标,具体我也不明白为啥要这样设计?
1 i = (n - 1) & hash
具体的put代码如下:
1 public V put(K key, V value) 2 { 3 //创建新节点 4 Node<K,V> p; int n, i; 5 //获取长度 6 n=table.length; 7 //对key值进行hash 8 int hash=hash(key); 9 //目前没有考虑hash冲突 10 table[i = (n - 1) & hash]=newNode(hash,key,value); 11 //返回原来的值 12 return value; 13 14 } 15 //创建新节点 16 Node<K,V> newNode(int hash, K key, V value) { 17 return new Node<>(hash, key, value); 18 }
删除操作-remove
删除操作其实和添加差不多,首先要获取下标,然后再将下标的值赋值为null。
1 public V remove(Object key) { 2 Node<K,V> e; 3 return (e = removeNode(hash(key), key)) == null ? 4 null : e.value; 5 } 6 /** 7 * 删除节点。 8 * 目前不考虑冲突,当对应的下标有值且不为null时,返回该值,并将对应的值设置为Null 9 * 这里考虑不太完善,因为hashmap是允许value为null的,这样并没有真正删除这个元素。 10 * @param hash 11 * @param key 12 * @return 13 */ 14 final Node<K,V> removeNode(int hash, Object key) { 15 Node<K,V> p;int index; 16 p=table[index = (table.length - 1) & hash]; 17 if(p!=null) 18 { 19 table[index]=null; 20 } 21 return p; 22 }
查询操作-get
和remove、put操作一样,也是需要根据key值获取下标,然后再返回对应的value值。
1 /** 2 * 根据key值,获取value值 3 * @param key 4 * @return 5 */ 6 public V get(Object key) { 7 Node<K,V> e; 8 return (e = getNode(hash(key), key)) == null ? null : e.value; 9 10 } 11 /** 12 * 获取对应的值 13 * @param hash 14 * @param key 15 * @return 16 */ 17 final Node<K,V> getNode(int hash, Object key) { 18 return table[(table.length - 1) & hash]; 19 }
是否存在该元素--containskey
根据key值判断是否存在该元素,其实和get操作一样,只是这里返回的是布尔值。
1 /** 2 * 是否存在对于的key值 3 * @param key 4 * @return 5 */ 6 public boolean containsKey(Object key) { 7 return getNode(hash(key), key) != null; 8 9 }
哇!是不是很简单呢?这里有两个疑问,就是不知道那个下标的获取和hash值得获取为啥要那样设计?是因为这样是最优的吗?下标的获取这种方法,发生冲突的几率就少很多吗?在整个系列中,我并没有讨论HashMap继承AbstractMap,实现Map接口。