• 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;  //存放的下一个节点,保持链表的连接性
    }
  • 相关阅读:
    Spring入门-对异常的处理
    Spring入门-Interceptor基本使用
    Spring入门-浏览器中文乱码问题
    Spring入门-使用SpringMVC完成一个登陆页面
    Spring入门-获取页面参数&向页面传值&重定向
    【Linux】【NodeJs】Centos7安装node-v10.16.3环境
    【Linux】【Go】Centos7安装go1.13环境
    【Linux】【Fabric】Centos7搭建Fabric运行环境
    【Linux】【ELK】利用elasticproxy对elasticsearch进行二次排序
    【Linux】【ELK】搭建Elasticsearch+Logstash+Kibana+Filebeat日志收集系统
  • 原文地址:https://www.cnblogs.com/qcq0703/p/12153419.html
Copyright © 2020-2023  润新知