学习资料:
- Java编程思想 P492
忍不住吐槽,这书翻译的真是 。。。
1. SimpleHashMap 简易HashMap <p>
/**
* 一个简易版的HashMap 用来帮助理解原理
*
* @author 英勇青铜5
*/
public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
private static final int SIZE = 2 << 3;
@SuppressWarnings("unchecked")
private LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];//LinkedList数组
/**
* 用来存放 Key 和 Value
*
*/
public V put(K key, V value) {
V oldValue = null;
//根据Key的hashCode值来确定出一个index
int index = Math.abs(key.hashCode()) % SIZE;
//判断在数组角标为index的位置上,是否有LinkedList对象
if (null == buckets[index]) {//没有
//根据index来创建LinkedList对象,并把LinkedList对象放入数组
buckets[index] = new LinkedList<MapEntry<K, V>>();
}
//在数组角标为index的位置上有LinkedList对象
LinkedList<MapEntry<K, V>> bucket = buckets[index];
//初始化一个MapEntry对象
MapEntry<K, V> pair = new MapEntry<K, V>(key, value);
//标记 判断是否已经将键值对添加进SimpleHashMap中
boolean found = false;
//迭代器
ListIterator<MapEntry<K, V>> iterator = bucket.listIterator();
//对LinkedList中的MapEntry进行遍历
while (iterator.hasNext()) {
MapEntry<K, V> entry = iterator.next();
//判断要存入的Key是否已经存在
if (entry.getKey().equals(key)) {//存在
oldValue = entry.getValue();//将旧的Value赋值给oldValue,方法最后返回用
iterator.set(pair);//替换MapEntry
found = true;//将标识设置为存在
break;
}
}
//遍历完 Key 不存在
if (!found) {
bucket.add(pair);//将MapEntry加入当前的LinkedList
}
return oldValue;
}
/**
* 通过 Key 来取 Value
*
*/
public V get(Object key) {
int index = Math.abs(key.hashCode()) % SIZE;
if (null == buckets[index]) {
return null;
}
for (MapEntry<K, V> entry : buckets[index]) {
if (entry.getKey().equals(key)) {
return entry.getValue();
}
}
return null;
}
/**
* AbstractMap必须实现的抽象方法
* 将Map.Entry对象存入Set集合
*/
@Override
public Set<Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> set = new HashSet<>();
//对LinkedList数组进行遍历
for (LinkedList<MapEntry<K, V>> bucket : buckets) {
if (null == bucket) {
continue;
}
for (MapEntry<K, V> entry : bucket) {
set.add(entry);
}
}
return set;
}
/**
* Map.Entry接口实现
* 用来保存和读取 Key,Value
*/
static class MapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public MapEntry(K key, V value) {
super();
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V v) {
V result = this.value;
this.value = v;
return result;
}
/**
* 得到HashCode值
* 将Key的hashCode值Value的hashCode值进行异或运算
*
*/
@Override
public int hashCode() {
return (null == key) ? 0 : key.hashCode() ^ ((null == value) ? 0 : value.hashCode());
}
/**
* 通过Key和Value来比较放入对象是否相同
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MapEntry))//不是MapEntry
return false;
@SuppressWarnings("rawtypes")
MapEntry me = (MapEntry) obj;
return null == key ? null == me.getKey(): key.equals(me.getKey())
&&
(null == value ? null == me.getValue() : value.equals(me.getValue()));
}
public String toString(){
return key + "==" +value;
}
}
}
private static final int SIZE = 2 << 3
,这里SIZE
大小其实无所谓,但由于后面有int index = Math.abs(key.hashCode()) % SIZE
,SIZE
为2的n
次方比较好
private LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE]
这个LinkedList
数组相当关键
查询一个Value
的速度受限于对Key
进行遍历一 一比较的速度,Hash
表可以将Key
存在某个地方,能够快速定位
存储一组元素最快的数据结构是数组。但数组一旦确定后,大小是固定的,不能修改,并且不同的Key
可能会产生相同的下表造成冲突。而SimpleHashMap
中想要保存的键值对数量是不确定的,通常使用外部链接
来解决冲突,将同一Hash
值得Key
都存入同一个LinkedList
中
LinkedList
数组内有2 << 3 = 16
个LinkedList
利用key.hashCode()
得到的值,进行% SIZE
运算可以保证index
肯定小于SZIE
,这样MapEntry
肯定能够找到一个LinkedList
来进行保存
同样根据Key
的hashCode
值计算得到的index
来进行查询时,通过buckets[index]
获取到Key
所在的LinkedList
,只需对这个确定的LinkedList
进行便利,而不用遍历所有的LinkedList
中的全部MapEntry
的key
都一 一比较,这样会比较高效
测试:
public class SimpleMapTest {
public static void main(String[] args) {
SimpleHashMap<Integer, String> s = new SimpleHashMap<>();
for (int i = 0; i < 5; i++) {
s.put(i, "这是" + i);
}
//打印Map
System.out.println(s);
//打印key为2 的value
System.out.println(s.get(2));
//打印Map.Entry
System.out.println(s.entrySet());
}
}
运行结果:
2.最后 <p>
在JDK 1.8
,HashMap
内部采用位桶+链表+红黑树
,当链表长度超过阈值(8)时,将链表转换为红黑树,深入学习可以看美团点评技术团队的Java 8系列之重新认识HashMap,写的非常好。不过,看了看,都啥鬼跟啥鬼,暂时丢一边,得先把数据结构补一补
本人很菜,有错误请指出
共勉 : )