废话不多说了,直接上源码
java8以后的红黑树扩容方法还待补充
首先定义一个基类,用于存放数据
public class Entry<K,V>{
//定义三个函数 k v 和 next指向
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;
}
public K getKey() {
return k;
}
public V getValue() {
return v;
}
}
然后新建MyHashMap方法,定义初始的几个类型
public class MyHashMap<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){
this(defaultLength,0.75);
}
public MyHashMap(int defaultLength,double defaultAddFactor){
if(defaultLength<0){
throw new IllegalArgumentException("数组异常");
}
if(defaultAddFactor<0){
throw new IllegalArgumentException("因子异常");
}
this.defaultLength = defaultLength;
this.defaultAddFactor = defaultAddFactor;
table = new Entry[defaultLength];
}
定义一个取模的方法,此处代码主要用于对新进入的参数进行取模,符合条件的添加在当前头指针的next链表中。
/**
* 使用每个object的hashCode计算hashCode
* @param hashCode
* @return hashCode
*/
private int hash(int hashCode){
hashCode = hashCode ^ ((hashCode >>> 20) ^ (hashCode >>> 12));
return hashCode ^ ((hashCode >>> 7) ^ hashCode >>> 4);
}
然后定义一个获取数字下标位置的函数
/**
* 获取保存位置的数组下标
*
* @param k
* @param length
* @return
*/
private int getIndex(K k, int length) {
int m = length - 1;
// 简单结构的map可以用下面的注释行代码代替。在扩容之前的理解更加透彻
// int index = k.hashCode() % m;
int index = hash(k.hashCode()) & m;//取模
return index >= 0 ? index : -index;
}
然后是存储的函数
/**
* 存
*
* @param k
* @param v
* @return
*/
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();
}
下来是取值的函数
/**
* 取值
*
* @param k
* @return
*/
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) {
// 此处涉及对entry进行轮询。如果当前层次没有查到。则跳到.next方法内
// 重复进行查询操作
if (k == entry.getKey() || k.equals(entry.getKey())) {
return entry.v;
} else {
entry = entry.next;
}
}
return null;
}
扩容函数
/**
* 扩容
*/
private void dilatation() {
// 如初始值为16,则会扩容到32
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];
//当且仅当entry不为空时,添加入list,并且把指针位置指向next
while (entry != null) {
list.add(entry);
entry = entry.next;
}
}
if (list.size() > 0) {
useSize = 0;
// 容量也会扩展到32
defaultLength = defaultLength * 2;
// 重新分配数组内存空间
table = newTable;
for (Entry<K, V> entry : list) {
//分离所有的entry
if (entry.next != null) {
entry.next = null;
}
put(entry.getKey(), entry.getValue());
}
}
}
全部代码如下:
import java.util.ArrayList;
import java.util.List;
public class MyHashMap<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){
this(defaultLength,0.75);
}
public MyHashMap(int defaultLength,double defaultAddFactor){
if(defaultLength<0){
throw new IllegalArgumentException("数组异常");
}
if(defaultAddFactor<0){
throw new IllegalArgumentException("因子异常");
}
this.defaultLength = defaultLength;
this.defaultAddFactor = defaultAddFactor;
table = new Entry[defaultLength];
}
/**
* 使用每个object的hashCode计算hashCode
* @param hashCode
* @return hashCode
*/
private int hash(int hashCode){
hashCode = hashCode ^ ((hashCode >>> 20) ^ (hashCode >>> 12));
return hashCode ^ ((hashCode >>> 7) ^ hashCode >>> 4);
}
/**
* 获取保存位置的数组下标
*
* @param k
* @param length
* @return
*/
private int getIndex(K k, int length) {
int m = length - 1;
// int index = k.hashCode() % m;
int index = hash(k.hashCode()) & m;//取模
return index >= 0 ? index : -index;
}
/**
* 存
*
* @param k
* @param v
* @return
*/
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();
}
/**
* 取值
*
* @param k
* @return
*/
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());
}
}
}
}
文末加个彩蛋,为什么阿里巴巴开发手册要求初始化HashMap的时候必须定义大小。经过对上述代码的测试,得到以下结果
扩容前
扩容后
很明显可以看到内存地址的变化。这里也可以解释清楚为什么需要进行初始化大小工作。