手写HashMap
Posted WilliamIT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写HashMap相关的知识,希望对你有一定的参考价值。
今日学习:
1、了解jdk1.8版的HashMap原理
2、手写jdk1.8版之前的HashMap
前言
好几天没有更了,前几天去外面旅游了几天,就没更新简书。趁今天有点时间就撸个简单的hashmap吧。
jdk 1.8版本之后的java中HashMap是数组+链表+红黑树实现的。在链表长度<8的时候都是一样的,当链表长度到达8时,该链表会转换成红黑树来存储节点。我个人感觉这位兄台的代码解析写的很棒,揭秘 HashMap 实现原理(Java 8)(http://www.cnblogs.com/yangming1996/p/7997468.html).
jdk 1.8之前就是没有红黑树的拉,也就是今天要手写的HashMap。
手撸
节点的基类接口
public interface BaseEntry <K,V>{ public K getKey();//获取键
public V getValue();//获取值}
实际上没啥东西就是把两个重要的方法提取了出来。
HashMap的基类接口
public interface BaseMap<K,V> { //存
public V put(K k, V v) ; //取
public V get(K k);
}
把HashMap中最重要的两个方法提取出来,一个添加,一个通过key得到value。
Entry类
public class Entry<K, V> implements BaseEntry<K, V> {
K k;
V v;
Entry<K, V> next; public Entry(K k, V v, Entry<K, V> next) { this.k = k; this.v = v; this.next = next;
} @Override
public K getKey() { return k;
} @Override
public V getValue() { return v;
}
}
HashMap是通过数组+链表,所以这边的节点是链表中的节点,存在next指向下一个节点。
HashMap类
public class MyHashMap<K, V> implements BaseMap<K, V>
属性
private int defaultLength = 16;//默认长度
private double defaultAddFactor = 0.75;//默认负载因子
private double useSize;//使用数组位置的数量
private Entry<K, V>[] table;//数组
构造方法
public MyHashMap() { this(16, 0.75);
} public MyHashMap(int defaultLength, double defaultAddFactor) { if (defaultLength < 0) { throw new IllegalArgumentException("数组异常");
} if (defaultAddFactor <= 0 || Double.isNaN(defaultAddFactor)) { throw new IllegalArgumentException("因子异常");
} this.defaultLength = defaultLength; this.defaultAddFactor = defaultAddFactor;
table = new Entry[defaultLength];
}
定义了两个构造方法,一个默认使用基本长度和负载因子。一个可自定义长度和负载因子。
计算hashCode
/** * 使用每个object的hashCode计算hashCode * @param hashCode * @return hashCode */
private int hash(int hashCode) {
hashCode = hashCode ^ ((hashCode >>> 20) ^ (hashCode >>> 12)); return hashCode ^ ((hashCode >>> 7) ^ hashCode >>> 4);
}
根据hashcode取模计算下标
/** * 获取保存位置的数组下标 * * @param k * @param length * @return */
private int getIndex(K k, int length) { int m = length - 1; int index = hash(k.hashCode()) & m;//取模
return index >= 0 ? index : -index;
}
每个key得到对应的HashCode 与长度取模,得到下标。
put(重要)
/** * 存 * * @param k * @param v * @return */
@Override
public V put(K k, V v) { if (useSize > defaultAddFactor * defaultLength) { //扩容
dilatation();
} //计算出下标
int index = getIndex(k, table.length);
Entry<K, V> entry = table[index];
Entry<K, V> newEntry = new Entry<>(k, v, null); if (entry == null) {
table[index] = newEntry;
useSize++;//table中有位置被占
} else {
Entry<K, V> t = entry; if (t.getKey() == k || (t.getKey() != null && t.getKey().equals(k))) {//相同key 对应修改当前value
t.v = v;
} else { while (t.next != null) { if (t.next.getKey() == k || (t.next.getKey() != null && t.next.getKey().equals(k))) {//相同key 对应修改当前value
t.next.v = v; break;
} else {
t = t.next;
}
} if (t.next == null) {
t.next = newEntry;
}
}
} return newEntry.getValue();
}
将得到的数据创建节点对象,计算出hashcode,得到对应下标,在对应数组的下标内,如果对应数组下标为null,直接将新的节点放入;反之,从链表头部开始遍历,如果存在相同key,说明是覆盖原来的值,反之将新节点插入链的尾部。
get(重要)
/** * 取值 * * @param k * @return */
@Override
public V get(K k) { int index = getIndex(k, table.length);
Entry<K, V> entry = table[index]; if (entry == null) { throw new NullPointerException();
} while (entry != null) { if (k == entry.getKey() || k.equals(entry.getKey())) { return entry.v;
} else {
entry = entry.next;
}
} return null;
}
得到下标,剩下的就是链表找值的问题。
扩容方法
/** * 扩容 */
private void dilatation() {
Entry<K, V>[] newTable = new Entry[defaultLength * 2]; List<Entry<K, V>> list = new ArrayList<>(); for (int i = 0; i < table.length; i++) { if (table[i] == null) continue; //遍历链表 添加到list
Entry<K, V> entry = table[i]; while (entry != null) { list.add(entry);
entry = entry.next;
}
} if (list.size() > 0) {
useSize = 0;
defaultLength = defaultLength * 2;
table = newTable; for (Entry<K, V> entry : list) { //分离所有的entry
if (entry.next != null) {
entry.next = null;
}
put(entry.getKey(), entry.getValue());
}
}
}
当数组使用的长度大于数组长度*负载因子,就需要扩容。
如何扩容呢?
1、得到所有的节点
2、重新put进入数组
今天的分享就到此为止了,希望能满意,如果有任何问题或者建议可以留言哦,我会及时回复,谢谢。
以上是关于手写HashMap的主要内容,如果未能解决你的问题,请参考以下文章
Java集合相关学习——手写一个简单的Map接口实现类(HashMap)