HashMap的内部实现-容器深入研究

来源:互联网 发布:mac删除空白页 编辑:程序博客网 时间:2024/05/22 10:43

HashMap键值对早已烂熟于心,但突然被问到HashMap内部是怎么实现的,我能说我不知道吗。。。

想想自己写一个的话也是可以实现的。

定义两个list分别存放key和value,然后实现一下put,get,set等方法即可。

import java.util.AbstractMap;import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Set;//HashMap也是继承的这个类AbstractMappublic class SlowMap<K, V> extends AbstractMap<K, V> {private List<K> keys = new ArrayList<K>();private List<V> values = new ArrayList<V>();public V put(K key, V value) {V oldValue = get(key);if(keys.contains(key)) {values.set(keys.indexOf(key), value);} else {keys.add(key);values.add(value);}return oldValue;}//public V get(K key)//属于覆盖 要和AbstractMap中get保持一致public V get(Object key) {if(keys.contains(key)) {//根据下标去values中去对应的valuereturn values.get(keys.indexOf(key));}return null;}//继承了就必须要实现的方法@Overridepublic Set<java.util.Map.Entry<K, V>> entrySet() {Set<java.util.Map.Entry<K, V>> set = new HashSet<java.util.Map.Entry<K, V>>();Iterator<K> ki = keys.iterator();Iterator<V> vi = values.iterator();while(ki.hasNext()) {//这里需要Map.Entry<K,V> 但是这个东西是个接口//所以需要自己实现一个set.add(new MapEntry<K, V>(ki.next(), vi.next()));}return set;}public static void main(String[] args) {SlowMap<String, String> map = new SlowMap<String, String>();map.put("1", "test1");map.put("2", "test2");map.put("3", "test3");map.put("4", "test4");map.put("5", "test5");System.out.println(map.get("4"));//在没有实现entrySet的时候会报错java.lang.NullPointerException//应该是toString里面调用了这个方法,然后再处理了一下格式System.out.println(map);//这个打印的格式是MapEntry里面的System.out.println(map.entrySet());}}

HashMap本身就是继承AbstractMap的,所以这里也实现为继承AbstractMap。但是继承这个的话就必须实现entrySet方法,这个方法是返回HashMap的Set集下面会细说。

先说一个重要的put和get方法

put中先查看keys中是否包含该key,如果有就替换它的value,没有就添加 并且将value添加到values中

get中看keys中是否包含该key,如果有根据key的下标去values中找对应的value,没有则返回null(HashMap中没有也是返回null)

注:keys和values之间是按插入顺序联系起来的,下标相同的就是对应的 “键值对”


put和get实现之后就可以测试了,其他的都很正常。但是直接打印map(自定义HashMap的实例),报了空指针,检查之后发现是因为没有实现entrySet这个方法,貌似toString中有调用这个方法,所有才出现了错误。

看方法中需要返回Set<Map.Entry>,但是这个MapEntry是个接口,所以我们需要自己实现一下,就像下面这样

import java.util.Map;public class MapEntry<K, V> implements Map.Entry<K, V> {private K key;private V value;public MapEntry(K key, V value) {this.key = key;this.value = value;}@Overridepublic K getKey() {// TODO Auto-generated method stubreturn key;}@Overridepublic V getValue() {// TODO Auto-generated method stubreturn value;}@Overridepublic V setValue(V arg0) {// TODO Auto-generated method stubvalue = arg0;return value;}public int hashCode() {return (key == null ? 0 : key.hashCode())^(value == null ? 0 : value.hashCode());}public boolean equals(Object o) {if(!(o instanceof MapEntry)) {return false;}MapEntry entry = (MapEntry) o;//想看懂这段需要点基本功啊...//保证key和value与entry的key vaule都相等才行return (key == null ?entry.getKey() == null : key.equals(entry.getKey())) &&   (value == null ?    entry.getValue() == null : value.equals(entry.getValue()));//if(key == entry.getKey() && value == entry.getValue())//return true;//if(key == null || value == null || entry.getKey() == null || entry.getValue() == null)//return false;//return false;}public String toString() {return key + " = " + value;}}

这里主要的就是实现hashCode和equals这两个方法,HashMap就是依靠他们来定位键值对的,所以很重要。

hashcode中用到了冒号运算符,如果相等就取冒号前面的值反之则去后面的。这个以还比较简单,看一下equals就不是那么简单了。。。

按照&&符号拆成两个,第一个如果key等于null,值就由entry.getKey()是否等于null决定,否则就有key是否等于entry.getKey()决定。

第二个和第一个类似,然后两个同时为true是才返回true。

也就是说必须满足两个key相同,两个value相同才行,都为null也可以


输出结果

test4{1=test1, 5=test5, 4=test4, 3=test3, 2=test2}[1 = test1, 5 = test5, 4 = test4, 3 = test3, 2 = test2]

这样就算完成一个HashMap了,但是这样的方式并不快,HashMap是以快著称的。这里的问题在于键的查询,这里对键的保存没有什么特定的顺序,所以只能线性查询(最慢的查询方式)。

HashMap用的是散列,它使得查询得以快速进行。散列将键存储在某处,以便能够快速找到。存储一组元素最快的数组结构是数组,所以可以使用它来保存键的信息。因为数组必须确定大小,所以这里不能保存键本身,键的数量是不确定的。这里是通过键生成一个数字(不超过数组大小)作为数组下标,这个数字就是散列码。就是hashCode()需要干的活。

也就是说现在直接通过hashCode()(下标)去找的话,可以找到很多个键,那怎么确定呢。这里的设计是,数组中存放的是键的list,确定list之后再通过键进行线性查找。(类似分块处理)

修改后的代码

import java.util.AbstractMap;import java.util.HashSet;import java.util.LinkedList;import java.util.ListIterator;import java.util.Set;public class SimpleHashMap<K, V> extends AbstractMap<K, V> {private final static int SIZE = 997;@SuppressWarnings("unchecked")LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];public V put(K key, V value) {V oldValue = get(key);//计算下标,保证不超过数组的大小int index = Math.abs(key.hashCode() % SIZE);if(buckets[index] == null) {//数组中每个元素都是listbuckets[index] = new LinkedList<MapEntry<K, V>>();}LinkedList<MapEntry<K, V>> bucket = buckets[index];MapEntry<K, V> pair = new MapEntry<K, V>(key, value);boolean found = false;ListIterator<MapEntry<K, V>> iterator = bucket.listIterator();while (iterator.hasNext()) {MapEntry<K, V> type = iterator.next();if(type.getKey().equals(key)){ oldValue = type.getValue();iterator.set(pair);found = true;break;}}if(!found) {buckets[index].add(pair);}return oldValue;}//public V get(K key)//属于覆盖 要和AbstractMap中get保持一致public V get(Object key) {int index = Math.abs(key.hashCode() % SIZE);if(buckets[index] == null) {return null;}for (MapEntry<K, V> pair : buckets[index]) {if(pair.getKey().equals(key)) {return pair.getValue();}}return null;}@Overridepublic Set<java.util.Map.Entry<K, V>> entrySet() {Set<java.util.Map.Entry<K, V>> set = new HashSet<java.util.Map.Entry<K, V>>();for(LinkedList<MapEntry<K, V>> bucket : buckets) {if(bucket == null) {continue;} else {for(MapEntry<K, V> pair : bucket) {set.add(pair);}}}return set;}public static void main(String[] args) {SimpleHashMap<String, String> map = new SimpleHashMap<String, String>();map.put("1", "test1");map.put("2", "test2");map.put("3", "test3");map.put("4", "test4");map.put("5", "test5");System.out.println(map.get("4"));//在没有实现entrySet的时候会报错java.lang.NullPointerException//应该是toString里面调用了这个方法,然后再处理了一下格式System.out.println(map);//这个打印的格式是MapEntry里面的System.out.println(map.entrySet());}}
put和get方法都变成了先通过下标查找对应的list,然后再从list中查看或添加对应的键值对。entrySet则是遍历数组以及list得到所有的键值对


0 0
原创粉丝点击