HashMap其实就是数组和单向链表的组合。先是数组,这里称之为位桶数组数组的每个元素就是一个单向链表,单向链表是从0到n的方向,每个节点包含下一个节点。数组的初始大小为16,可自行扩容,在数组大小为数组长
度跟0.75相乘的值时候,就会进行扩容,扩大为原来的两倍,也就是32.每个数组的下标hash值,是通过map中key的hashCode值跟位桶数组长度的位运算所得,这个算法可以有很多种。
还是看代码实现吧,都有详细解释
package com.interact.firewall;
public class TestMap { //map由数组和单向链表组成
Node [] table; //位桶数组,存放生成的节点
int size; //计算map的长度
public TestMap(){
table = new Node [16]; //默认长度为16
size = 0;
}
public void put(Object key,Object value){ //给map中存放数值
Node newNode = new Node();
newNode.hash = getHash(key.hashCode(),table.length);
newNode.key = key;
newNode.value = value;
newNode.nextNode = null;
Node temp = table[newNode.hash]; //拿到当前节点,进行判断
Node endNode = new Node(); //将当前temp值进行保存,便于数据处理
boolean isTrue = true; //这个boolean值代表 如果是key已经存在时,只需要改变value值即可,其他的不用变
if(temp == null){ //等于空则代表是存放的第一个节点值
table[newNode.hash] = newNode;
newNode.nextNode = null; //在放进去第一值的时候,下一个节点自然为空,后续需要处理
}else{ //如果不是位桶数组的第一个节点,需要进行处理,遍历对应链表查找到最后一个,放在其最后
while (temp != null){ //当节点不为空的时候,进行遍历
if(temp.key.equals(key)){ //如果key重复的话,将原有的值重新赋值即可
isTrue = true;
temp.value = newNode.value;
break; //结束循环
}else{
isTrue = false;
endNode = temp;
temp = temp.nextNode;
}
}
if(!isTrue){ //只有不存在key的值的时候,才会有以下步骤,对循环到的最后一个节点的下一个节点进行赋值
endNode.nextNode = newNode;
}
}
size++;
}
public String toString(){
StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < table.length; i++) { //首先遍历最外层的位桶数组
Node temp = table[i];
while (temp != null){ //然后遍历位桶数组的链表,进行拼接得到key,value值
sb.append(temp.key+":"+temp.value+",");
temp = temp.nextNode; //并将temp值重新定义,知道该下标的所有子节点便利完成
}
}
sb.setCharAt(sb.length()-1,'}');
return sb.toString();
}
public Object get(Object key){
Object object = null;
int hash = getHash((int)key,table.length); //通过key获取到对应的hash值
if(table[hash] != null){ //进行判断,如果位桶数组对应的hash值下标为空,那么返回就是空
Node temp = table[hash]; //不为空时,拿到位桶数组该下标的节点
while(temp != null){ //将该下标的节点进行循环判断
if(temp.key.equals(key)){ //循环判断该下标节点的key是不是想要获取的
object = temp.value; //如果一样,则拿到返回值,并通过break结束循环
break;
}else{ //如果该下标的第一个节点的key不是想要获取的,将temp重新赋值为下一个节点,一直循环到拿到想要获取的key为止
temp = temp.nextNode;
}
}
}
return object;
}
public static int getHash(int hashCode,int length){ //获取hash值,也就是位桶数组中的下标
System.out.println("通过取余计算hash值:"+hashCode%(length-1));
System.out.println("通过位运算计算hash值:"+(hashCode&(length-1)));
return hashCode&(length-1);
}
public static void main(String[] args) {
TestMap map = new TestMap();
map.put(3,"测试1"); //相同的key会被覆盖
map.put(3,"测试3");
map.put(79,"测试79并且hash值都是15"); //相同的hash值,会形成链表,在后边添加
map.put(95,"测试95并且hash值都是15");
map.put(63,"测试63并且hash值都是15");
map.put(1,"测试4");
for (int i = 0; i <= 100; i++) { //找到相同的hash值
System.out.println(i+"----"+getHash(i,16));
}
System.out.println(map.get(63));
System.out.println(map.get(3));
System.out.println(map.toString());
}
}
class Node{ //定义链表的节点
public Object key; //节点中包含key值
public Object value; //value值
public int hash; //hash值,这个值不是hashCode值,是hashCode跟数组长度取余或者位运算的到的值,代表在数组中存放的下标
public Node nextNode; //存放的下一个节点,保持链表的连接性
}
以下是优化添加泛型的版本
package com.interact.firewall;
import java.util.HashMap;
public class TestMap<K,V> { //map由数组和单向链表组成
Node [] table; //位桶数组,存放生成的节点
int size; //计算map的长度
public TestMap(){
table = new Node [16]; //默认长度为16
size = 0;
}
public void put(K key,V value){ //给map中存放数值
Node newNode = new Node();
newNode.hash = getHash(key.hashCode(),table.length);
newNode.key = key;
newNode.value = value;
newNode.nextNode = null;
Node temp = table[newNode.hash]; //拿到当前节点,进行判断
Node endNode = new Node(); //将当前temp值进行保存,便于数据处理
boolean isTrue = true; //这个boolean值代表 如果是key已经存在时,只需要改变value值即可,其他的不用变
if(temp == null){ //等于空则代表是存放的第一个节点值
table[newNode.hash] = newNode;
newNode.nextNode = null; //在放进去第一值的时候,下一个节点自然为空,后续需要处理
size++;
}else{ //如果不是位桶数组的第一个节点,需要进行处理,遍历对应链表查找到最后一个,放在其最后
while (temp != null){ //当节点不为空的时候,进行遍历
if(temp.key.equals(key)){ //如果key重复的话,将原有的值重新赋值即可
isTrue = true;
temp.value = newNode.value;
break; //结束循环
}else{
isTrue = false;
endNode = temp; //保存最后一个节点,以供后边步骤的使用
temp = temp.nextNode;
}
}
if(!isTrue){ //只有不存在key的值的时候,才会有以下步骤,对循环到的最后一个节点的下一个节点进行赋值
endNode.nextNode = newNode;
size++; //切记,只有在添加新的值时,才进行size++
}
}
}
public String toString(){
StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < table.length; i++) { //首先遍历最外层的位桶数组
Node temp = table[i];
while (temp != null){ //然后遍历位桶数组的链表,进行拼接得到key,value值
sb.append(temp.key+":"+temp.value+",");
temp = temp.nextNode; //并将temp值重新定义,知道该下标的所有子节点便利完成
}
}
sb.setCharAt(sb.length()-1,'}');
return sb.toString();
}
public V get(K key){
V object = null;
int hash = getHash(key.hashCode(),table.length); //通过key获取到对应的hash值
if(table[hash] != null){ //进行判断,如果位桶数组对应的hash值下标为空,那么返回就是空
Node temp = table[hash]; //不为空时,拿到位桶数组该下标的节点
while(temp != null){ //将该下标的节点进行循环判断
if(temp.key.equals(key)){ //循环判断该下标节点的key是不是想要获取的
object = (V)temp.value; //如果一样,则拿到返回值,并通过break结束循环
break;
}else{ //如果该下标的第一个节点的key不是想要获取的,将temp重新赋值为下一个节点,一直循环到拿到想要获取的key为止
temp = temp.nextNode;
}
}
}
return object;
}
public static int getHash(int hashCode,int length){ //获取hash值,也就是位桶数组中的下标
System.out.println("通过取余计算hash值:"+hashCode%(length-1));
System.out.println("通过位运算计算hash值:"+(hashCode&(length-1)));
return hashCode&(length-1);
}
public static void main(String[] args) {
TestMap<Integer,String> map = new TestMap();
map.put(3,"测试1"); //相同的key会被覆盖
map.put(3,"测试3");
map.put(79,"测试79并且hash值都是15"); //相同的hash值,会形成链表,在后边添加
map.put(95,"测试95并且hash值都是15");
map.put(63,"测试63并且hash值都是15");
map.put(1,"测试4");
for (int i = 0; i <= 100; i++) { //找到相同的hash值
System.out.println(i+"----"+getHash(i,16));
}
System.out.println(map.get(63));
System.out.println(map.get(3));
System.out.println(map.toString());
}
}
class Node<K,V>{ //定义链表的节点
public K key; //节点中包含key值
public V value; //value值
public int hash; //hash值,这个值不是hashCode值,是hashCode跟数组长度取余或者位运算的到的值,代表在数组中存放的下标
public Node nextNode; //存放的下一个节点,保持链表的连接性
}