HashMap是由数组和链表来实现对数据的存储,它们各有特点:
- 数组:占用空间连续,寻址容易,查询速度快。但是,增加和删除效率 非常低。
- 链表:占用空间不连续,寻址困难,查询速度慢。但是,增加和删除效率非常高。
HashMap通过使用”哈希表“结合了数组和链表的优点,哈希表的本质就是“数组+链表”。
手动实现HashMap
1.创建结点类
package cn.GTMStudio.MyConnection;
/**
* 用于GTMHashMap中
*/
public class Node2 {
int hash;
Object key;
Object value;
//下一个结点
Node2 next;
}
2. 实现了put方法增加键值对,并解决了键重复时覆盖相应的结点
package cn.GTMStudio.MyConnection;
/**
* 自定义一个HashMap
* 实现了put方法增加键值对,并解决了键重复时覆盖相应的结点
*
* @author henrly
*/
@SuppressWarnings("all")
public class GTMHashMap01 {
Node2[] table;//位桶数组
int size;//存放的键值对的个数
public GTMHashMap01() {
this.table = new Node2[16];//长度一般定义为2的整数幂
}
public void put(Object key, Object value) {
//定义新的节点对象
Node2 newNode = new Node2();
newNode.hash = myHash(key.hashCode(), table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
Node2 temp = table[newNode.hash];
boolean keyRepeat = false;
Node2 iterLast = null;//正在遍历的最后一个元素
if (temp == null) {
//此处数组元素为空,则直接将新节点放进去
table[newNode.hash] = newNode;
} else {
//此处数组元素不为空,则遍历对应链表
//位桶数组的作用体现
while (temp != null) {
//判断key,如果重复,则覆盖
if (temp.key.equals(key)) {
keyRepeat = true;
temp.value = value;//只是覆盖value即可,其它的值(hash,key,next)保持不变
break;//结束循环
} else {
//key不重复
iterLast = temp;//保存最后一个元素
temp = temp.next;
}
}
if (!keyRepeat) {
//若没有发生key重复的情况,则添加到链表最后
iterLast.next = newNode;//插入链表
}
}
}
/**
* 计算hash值
*
* @param v
* @param length
* @return
*/
public static int myHash(int v, int length) {
//直接位运算,效率高
System.out.println("hash in myHash: " + (v & (length - 1)));
return v & (length - 1);
}
public static void main(String[] args) {
GTMHashMap01 m = new GTMHashMap01();
m.put(10, "aa");
m.put(20, "bb");
m.put(30, "cc");
m.put(20, "sssss");
m.put(53, "533");
m.put(69, "69999");
m.put(85, "8555");
System.out.println(m);
}
}
3. 实现toString方法,方便查看Map中的键值对信息
package cn.GTMStudio.MyConnection;
/**
* 自定义一个HashMap
* 实现toString方法,方便查看Map中的键值对信息
*
* @author henrly
*/
@SuppressWarnings("all")
public class GTMHashMap02 {
Node2[] table;//位桶数组
int size;//存放的键值对的个数
public GTMHashMap02() {
this.table = new Node2[16];//长度一般定义为2的整数幂
}
public void put(Object key, Object value) {
//定义新的节点对象
Node2 newNode = new Node2();
newNode.hash = myHash(key.hashCode(), table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
Node2 temp = table[newNode.hash];
boolean keyRepeat = false;
Node2 iterLast = null;//正在遍历的最后一个元素
if (temp == null) {
//此处数组元素为空,则直接将新节点放进去
table[newNode.hash] = newNode;
size++;
} else {
//此处数组元素不为空,则遍历对应链表
//位桶数组的作用体现
while (temp != null) {
//判断key,如果重复,则覆盖
if (temp.key.equals(key)) {
keyRepeat = true;
temp.value = value;//只是覆盖value即可,其它的值(hash,key,next)保持不变
break;//结束循环
} else {
//key不重复
iterLast = temp;//保存最后一个元素
temp = temp.next;
}
}
if (!keyRepeat) {
//若没有发生key重复的情况,则添加到链表最后
iterLast.next = newNode;//插入链表
size++;
}
}
}
@Override
public String toString() {
//{10:aa,20:bb}
StringBuilder sb = new StringBuilder("{");
//遍历位桶数组
for (int i = 0; i < table.length; i++) {
Node2 temp = table[i];
//遍历链表
while (temp != null) {
sb.append(temp.key + ":" + temp.value + ",");
temp = temp.next;
}
}
sb.setCharAt(sb.length() - 1, '}');
return sb.toString();
}
/**
* 计算hash值
*
* @param v
* @param length
* @return
*/
public static int myHash(int v, int length) {
//直接位运算,效率高
System.out.println("hash in myHash: " + (v & (length - 1)));
return v & (length - 1);
}
public static void main(String[] args) {
GTMHashMap02 m = new GTMHashMap02();
m.put(10, "aa");
m.put(20, "bb");
m.put(30, "cc");
m.put(20, "sssss");
m.put(53, "533");
m.put(69, "69999");
m.put(85, "8555");
System.out.println(m);
}
}
4. 实现get方法,根据键对象获取对应的值对象
package cn.GTMStudio.MyConnection;
/**
* 自定义一个HashMap
* 实现get方法,根据键对象获取对应的值对象
*
* @author henrly
*/
@SuppressWarnings("all")
public class GTMHashMap03 {
Node2[] table;//位桶数组
int size;//存放的键值对的个数
public GTMHashMap03() {
this.table = new Node2[16];//长度一般定义为2的整数幂
}
public Object get(Object key) {
//获取hash值
int hash = myHash(key.hashCode(), table.length);
Node2 temp = table[hash];
Object value = null;
//遍历链表
while (temp != null) {
if (temp.key.equals(key)) {
value = temp.value;
break;
} else {
temp = temp.next;
}
}
return value;
}
public void put(Object key, Object value) {
//如果要完善,还需要考虑数组扩容的问题!!!
//定义新的节点对象
Node2 newNode = new Node2();
newNode.hash = myHash(key.hashCode(), table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
Node2 temp = table[newNode.hash];
boolean keyRepeat = false;
Node2 iterLast = null;//正在遍历的最后一个元素
if (temp == null) {
//此处数组元素为空,则直接将新节点放进去
table[newNode.hash] = newNode;
size++;
} else {
//此处数组元素不为空,则遍历对应链表
//位桶数组的作用体现
while (temp != null) {
//判断key,如果重复,则覆盖
if (temp.key.equals(key)) {
keyRepeat = true;
temp.value = value;//只是覆盖value即可,其它的值(hash,key,next)保持不变
break;//结束循环
} else {
//key不重复
iterLast = temp;//保存最后一个元素
temp = temp.next;
}
}
if (!keyRepeat) {
//若没有发生key重复的情况,则添加到链表最后
iterLast.next = newNode;//插入链表
size++;
}
}
}
@Override
public String toString() {
//{10:aa,20:bb}
StringBuilder sb = new StringBuilder("{");
//遍历位桶数组
for (int i = 0; i < table.length; i++) {
Node2 temp = table[i];
//遍历链表
while (temp != null) {
sb.append(temp.key + ":" + temp.value + ",");
temp = temp.next;
}
}
sb.setCharAt(sb.length() - 1, '}');
return sb.toString();
}
/**
* 计算hash值
*
* @param v
* @param length
* @return
*/
public static int myHash(int v, int length) {
//直接位运算,效率高
return v & (length - 1);
}
public static void main(String[] args) {
GTMHashMap03 m = new GTMHashMap03();
m.put(10, "aa");
m.put(20, "bb");
m.put(30, "cc");
m.put(20, "sssss");
m.put(53, "533");
m.put(69, "69999");
m.put(85, "8555");
System.out.println(m);
System.out.println(m.get(53));
}
}
5.完善封装,增加泛型
package cn.GTMStudio.MyConnection;
import java.util.Map;
/**
* 自定义一个HashMap
* 完善封装,增加泛型
*
* @author henrly
*/
@SuppressWarnings("all")
public class GTMHashMap04<K,V> {
Node3[] table;//位桶数组
int size;//存放的键值对的个数
public GTMHashMap04() {
this.table = new Node3[16];//长度一般定义为2的整数幂
}
public V get(K key) {
//获取hash值
int hash = myHash(key.hashCode(), table.length);
Node3 temp = table[hash];
V value = null;
//遍历链表
while (temp != null) {
if (temp.key.equals(key)) {
value = (V)temp.value;
break;
} else {
temp = temp.next;
}
}
return value;
}
public void put(K key, V value) {
//如果要完善,还需要考虑数组扩容的问题!!!
//定义新的节点对象
Node3 newNode = new Node3();
newNode.hash = myHash(key.hashCode(), table.length);
newNode.key = key;
newNode.value = value;
newNode.next = null;
Node3 temp = table[newNode.hash];
boolean keyRepeat = false;
Node3 iterLast = null;//正在遍历的最后一个元素
if (temp == null) {
//此处数组元素为空,则直接将新节点放进去
table[newNode.hash] = newNode;
size++;
} else {
//此处数组元素不为空,则遍历对应链表
//位桶数组的作用体现
while (temp != null) {
//判断key,如果重复,则覆盖
if (temp.key.equals(key)) {
keyRepeat = true;
temp.value = value;//只是覆盖value即可,其它的值(hash,key,next)保持不变
break;//结束循环
} else {
//key不重复
iterLast = temp;//保存最后一个元素
temp = temp.next;
}
}
if (!keyRepeat) {
//若没有发生key重复的情况,则添加到链表最后
iterLast.next = newNode;//插入链表
size++;
}
}
}
@Override
public String toString() {
//{10:aa,20:bb}
StringBuilder sb = new StringBuilder("{");
//遍历位桶数组
for (int i = 0; i < table.length; i++) {
Node3 temp = table[i];
//遍历链表
while (temp != null) {
sb.append(temp.key + ":" + temp.value + ",");
temp = temp.next;
}
}
sb.setCharAt(sb.length() - 1, '}');
return sb.toString();
}
/**
* 计算hash值
*
* @param v
* @param length
* @return
*/
public static int myHash(int v, int length) {
//直接位运算,效率高
return v & (length - 1);
}
public static void main(String[] args) {
GTMHashMap04<Integer,String> m = new GTMHashMap04();
m.put(10, "aa");
m.put(20, "bb");
m.put(30, "cc");
m.put(20, "sssss");
m.put(53, "533");
m.put(69, "69999");
m.put(85, "8555");
System.out.println(m);
System.out.println(m.get(53));
}
}
总结
HashMap底层是由数组和链表实现的,对HashMap的操作实际上就是先遍历数组再遍历链表,掌握这一点就比较容易掌握HashMap。