今日学习:
1、了解jdk1.8版的HashMap原理
2、手写jdk1.8版之前的HashMap
前言
好几天没有更了,前几天去外面旅游了几天,就没更新简书。趁今天有点时间就撸个简单的hashmap吧。
jdk 1.8版本之后的java中HashMap是数组+链表+红黑树实现的。在链表长度<8的时候都是一样的,当链表长度到达8时,该链表会转换成红黑树来存储节点。我个人感觉这位兄台的代码解析写的很棒,揭秘 HashMap 实现原理(Java 8).
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进入数组
今天的分享就到此为止了,希望能满意,如果有任何问题或者建议可以留言哦,我会及时回复,谢谢。
感谢阅读
本人微信公众号:
微信公众号二维码.jpg
网友评论