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也是继承的这个类AbstractMap
public 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中去对应的value
return values.get(keys.indexOf(key));
}
return null;
}
//继承了就必须要实现的方法
@Override
public 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;
}
@Override
public K getKey() {
// TODO Auto-generated method stub
return key;
}
@Override
public V getValue() {
// TODO Auto-generated method stub
return value;
}
@Override
public V setValue(V arg0) {
// TODO Auto-generated method stub
value = 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) {
//数组中每个元素都是list
buckets[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;
}
@Override
public 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得到所有的键值对