HashMap的简单实现

Posted qcq0703

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap的简单实现相关的知识,希望对你有一定的参考价值。

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;  //存放的下一个节点,保持链表的连接性
}

 

以上是关于HashMap的简单实现的主要内容,如果未能解决你的问题,请参考以下文章

代码片段 - Golang 实现简单的 Web 服务器

包含不同片段的HashMap(或ArrayList)

Java集合相关学习——手写一个简单的Map接口实现类(HashMap)

Java集合相关学习——手写一个简单的Map接口实现类(HashMap)

HashMap 和 ConcurrentHashMap 的区别

HashMap简单实现