一、实现简易HashMap
作为讲解用,不可用作生产环境!
第一步,把key实体转化为一定范围内的数字。
我们知道Java中所有的数据类型可以以对象的形式呈现的。
而且它为每个对象赋予了一个编号(即 hashCode()函数的返回值)
这样我们就能使用某种方法将这个代表对象的编号转化为一个一定范围内的数字。(为了讲解方便我们暂时把这个范围设置为0~9)
private int hash(Object obj) {
int h = obj.hashCode();
// hashCode有可能是负数,如果是负数则取绝对值
if(0 > h) {
h *= -1;
}
return h%10
}
复制代码
第二步,设计数据的存储方式
我们把对象编号转化为了0~9中的一个数字,那么必然会出现重复。
为了处理重复的值我们定义一个长度为10的数组。每个数组存的值是一条链表。
每当转化后的数字重复时就在对应的链表后追加一个值。
public class Node {
// 对象
public Object key;
public Object value;
// 下一节点
public Node next;
}
Node store[] = new Node[10];
复制代码
第三步,实现设置功能 、 获取功能 和 main函数
- 源码地址:github.com/dawnchengx/…
- 创建存储节点类 Node.java
public class Node {
// 对象
public Object key;
public Object value;
// 下一节点
public Node next;
public Node(Object key, Object value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
复制代码
- 创建HashMap类 HashMap.java
public class HashMap {
Node store[] = new Node[10];
private int hash(Object obj) {
int h = obj.hashCode();
if(0 > h) {
h *= -1;
}
return h%10;
}
// 设置方法
public void put(Object key, Object value) {
// 确定该对象放入数组中的位置
int position = this.hash(key);
if (null == this.store[position]) {
this.store[position] = new Node(key, value, null);
return;
}
// 放入指定位置
Node current = this.store[position];
while(true) {
if(current.key.equals(key)) {
current.value = value;
break;
}
if(null == current.next) {
current.next = new Node(key, value, null);
break;
}
current = current.next;
}
}
// 获取方法
public Object get(Object key) {
// 确定该对象放置的数组位置
int position = this.hash(key);
if (null == this.store[position]) {
return null;
}
Node current = this.store[position];
while(true) {
if(current.key.equals(key)) {
return current.value;
}
if(null == current.next) {
return null;
}
current = current.next;
}
}
}
复制代码
- 创建Main类 Main.java
public class Main {
public static void main(String[] args) {
HashMap myHM = new HashMap();
// 测试基础功能
myHM.put("hello", "world");
myHM.put("foo", "bar");
System.out.println(myHM.get("hello"));
System.out.println(myHM.get("foo"));
// 查询不存在的数据
System.out.println(myHM.get("some"));
}
}
复制代码
- 编译运行Main.java
# 编译运行Main.java
javac Main.java && java Main
# 输出
world
bar
null
复制代码
二、扩容
由于HashMap会把对象的hashCode转化为在一定范围内数字,所以对象会过于集中于数组中几个位置。
这样会导致链表变得非常长,减少检索效率。为了在结构上提升效率,所以我们设定一个阀门值,比如数组大小的0.75。当数组内的值数达到数组总长度的0.75时,自动触发扩容操作。
- 源码地址:github.com/dawnchengx/…
- 修改HashMap类 HashMap.java
public class HashMap {
private int defaultVal;
private double scaleFactor;
private Node store[];
public HashMap() {
this.defaultVal = 16;
this.scaleFactor = 0.75;
this.store = new Node[defaultVal];
}
public HashMap(int defaultVal, float scaleFactor) {
this.defaultVal = defaultVal;
this.scaleFactor = scaleFactor;
this.store = new Node[defaultVal];
}
private int hash(Object obj) {
int h = obj.hashCode();
if(0 > h) {
h *= -1;
}
return h%this.defaultVal;
}
// 设置方法
public void put(Object key, Object value) {
// 确定该对象放入数组中的位置
int position = this.hash(key);
if (null == this.store[position]) {
this.store[position] = new Node(key, value, null);
return;
}
// 放入指定位置
Node current = this.store[position];
while(true) {
if(current.key.equals(key)) {
current.value = value;
break;
}
if(null == current.next) {
current.next = new Node(key, value, null);
break;
}
current = current.next;
}
// 获得数组非空值
int notNullNum = this.defaultVal;
for(int i = 0; i < this.defaultVal; i++) {
if (null == this.store[i]) {
notNullNum--;
}
}
// 达到阀门值,自动触发扩容
if ( notNullNum > (int)(this.defaultVal * this.scaleFactor) ) {
this.resize(2);
}
}
// 获取方法
public Object get(Object key) {
// 确定该对象放置的数组位置
int position = this.hash(key);
if (null == this.store[position]) {
return null;
}
Node current = this.store[position];
while(true) {
if(current.key.equals(key)) {
return current.value;
}
if(null == current.next) {
return null;
}
current = current.next;
}
}
// 扩容方法
public void resize(int multiplier) {
int oldDefaultVal = this.defaultVal;
this.defaultVal *= multiplier;
Node newStore[] = new Node[this.defaultVal];
for(int i = 0; i < oldDefaultVal; i++) {
if(null != this.store[i]) {
newStore[this.hash(this.store[i].key)] = this.store[i];
}
}
this.store= newStore;
}
// 打印扩容状态
public void testResize() {
System.out.printf("当前数组大小为%d\n", this.defaultVal);
for(int i = 0; i < this.defaultVal; i++) {
System.out.printf("第%d位:", i);
Node current = this.store[i];
int j = 0;
while(true) {
if(null != current) {
System.out.printf("%d->[%s]=%s ", j, current.key, current.value);
}else {
System.out.printf("该位无值");
break;
}
if(null == current.next) {
break;
}
current = current.next;
j++;
}
System.out.println();
}
System.out.println();
}
}
复制代码
- 修改Main类 Main.java
public class Main {
public static void main(String[] args) {
HashMap myHM = new HashMap();
// 测试基础功能
myHM.put("hello", "world");
myHM.put("foo", "bar");
System.out.println(myHM.get("hello"));
System.out.println(myHM.get("foo"));
// 查询不存在的数据
System.out.println(myHM.get("some"));
// 添加足够的值触发第一次扩容
for(int i = 0; i < 100; i++) {
String key = "key"+i;
String value = "value"+i;
myHM.put(key, value);
}
// 打印扩容后的数据存储情况
myHM.testResize();
// 添加足够的值触发第二次扩容
for(int i = 100; i < 150; i++) {
String key = "key"+i;
String value = "value"+i;
myHM.put(key, value);
}
// 打印扩容后的数据存储情况
myHM.testResize();
}
}
复制代码
三、链表过长时转化为红黑树
链表查询、插入和删除的渐近增长表达式为O(n),随着链表的变长这些操作会越来越慢。
为了当链表过长时不影响增删查的效率,当链表长度大于某个值是,我们把该链表转化为红黑树。
由于TreeMap是由红黑树实现,我们就直接用TreeMap作为红黑树的结构使用。
-
修改HashMap类 HashMap.java
import java.util.TreeMap;
import java.util.Iterator;
public class HashMap {
private int defaultVal;
private double scaleFactor;
private Node store[];
private int toBRTreeVal = 4;
public HashMap() {
this.defaultVal = 16;
this.scaleFactor = 0.75;
this.store = new Node[defaultVal];
}
public HashMap(int defaultVal, float scaleFactor) {
this.defaultVal = defaultVal;
this.scaleFactor = scaleFactor;
this.store = new Node[defaultVal];
}
private int hash(Object obj) {
int h = obj.hashCode();
if(0 > h) {
h *= -1;
}
return h%this.defaultVal;
}
// 设置方法
public void put(Object key, Object value) {
// 确定该对象放入数组中的位置
int position = this.hash(key);
if (null == this.store[position]) {
this.store[position] = new Node(key, value, null);
return;
}
// 放入指定位置
Node current = this.store[position];
int linkLen = 0;
while(true) {
linkLen++;
if(current.key.equals(key)) {
current.value = value;
break;
}
if(null == current.next) {
current.next = new Node(key, value, null);
break;
}
current = current.next;
}
if (this.toBRTreeVal < linkLen) {
TreeMap treeMap = new TreeMap();
while(true) {
treeMap.put(current.key, current.value);
if(null == current.next) {
break;
}
current = current.next;
}
this.store[position].key = "BRTree";
this.store[position].value = treeMap;
}
// 获得数组非空值
int notNullNum = this.defaultVal;
for(int i = 0; i < this.defaultVal; i++) {
if (null == this.store[i]) {
notNullNum--;
}
}
// 达到阀门值,自动触发扩容
if ( notNullNum > (int)(this.defaultVal * this.scaleFactor) ) {
this.resize(2);
}
}
// 获取方法
public Object get(Object key) {
// 确定该对象放置的数组位置
int position = this.hash(key);
if (null == this.store[position]) {
return null;
}
Node current = this.store[position];
while(true) {
if("RBTree" == current.key && current.value instanceof TreeMap) {
TreeMap currentTreeMap = (TreeMap)current.value;
return currentTreeMap.get(key);
}
if(current.key.equals(key)) {
return current.value;
}
if(null == current.next) {
return null;
}
current = current.next;
}
}
// 扩容方法
public void resize(int multiplier) {
int oldDefaultVal = this.defaultVal;
this.defaultVal *= multiplier;
Node newStore[] = new Node[this.defaultVal];
for(int i = 0; i < oldDefaultVal; i++) {
if(null != this.store[i]) {
Node current = this.store[i];
if (current.key == "RBTree" && current.value instanceof TreeMap) {
TreeMap currentTreeMap = (TreeMap)current.value;
newStore[this.hash(currentTreeMap.firstKey())] = current;
} else if (current instanceof Node) {
newStore[this.hash(current.key)] = current;
}
}
}
this.store= newStore;
}
// 打印转化状况
public void testTrans() {
System.out.printf("当前数组大小为%d\n", this.defaultVal);
for(int i = 0; i < this.defaultVal; i++) {
System.out.printf("第%d位:", i);
Node current = this.store[i];
if (null != current && current.key == "RBTree" && current.value instanceof TreeMap) {
TreeMap currentTreeMap = (TreeMap)current.value;
Iterator it = currentTreeMap.keySet().iterator();
while (it.hasNext()) {
System.out.printf("BRTree:[%s]=%s", it.next(), currentTreeMap.get(it.next()));
}
} else if (current instanceof Node) {
int j = 0;
while(true) {
if(null != current) {
System.out.printf("link:%d->[%s]=%s ", j, current.key, current.value);
}else {
System.out.printf("该位无值");
break;
}
if(null == current.next) {
break;
}
current = current.next;
j++;
}
} else {
System.out.printf("无");
}
System.out.println();
}
System.out.println();
}
}
复制代码
- 修改Main类 Main.java
public class Main {
public static void main(String[] args) {
HashMap myHM = new HashMap();
// 测试基础功能
myHM.put("hello", "world");
myHM.put("foo", "bar");
System.out.println(myHM.get("hello"));
System.out.println(myHM.get("foo"));
// 查询不存在的数据
System.out.println(myHM.get("some"));
// 添加足量值触发红黑树
for(int i = 0; i < 300; i++) {
String key = "key"+i;
String value = "value"+i;
myHM.put(key, value);
}
// 打印转换情况
myHM.testTrans();
}
}
复制代码
四、总结
本文通过简易的代码阐述了Java类库HashMap的基础原理,如果想要更深一步学习请移步https://github.com/Snailclimb/JavaGuide/blob/master/Java/HashMap.md。
并参照源码学习。