纯手写实现HashMap

Posted chenfei-java

tags:

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

1.hashmap的实现

  ① 初始化

    1)定义一个Node<K, V>的数组来存放元素,但不立即初始化,在使用的时候再加载

    2)定义数组初始大小为16

    3)定义负载因子,默认为0.75,

    4)定义size用来记录容器存放的元素数量

  ② put的实现思路

    1)  判断容器是否为空,为空则初始化。

    2)判断容器的size是否大于阀值,是的话就扩容为以前长度的两倍,并重新计算其中元素的存放位置,进行重新存放

    3)计算出key的index角标位置

    4)判断计算出的index位置是否存在元素,存在的话则遍历链表,判断key是否存在,存在则更新,不存在则增加

  ③ get的实现思路

    1)通过key计算出它所在的index

    2)遍历index位置处的链表,并获取value返回。

package com.test;

/**
 * 自定义hashMap
 * @author cf
 *
 * @param <K>
 * @param <V>
 */
public class MyHashMap<K, V> implements MyMap<K, V>{
    //1.定义一个容器用来存放元素, 但不立即初始化,使用懒加载方式
    Node<K, V>[] table = null;
    
    //2.定义容器的默认大小
    static int DEFAULT_INITIAL_CAPACITY = 16;
    
    //3.HashMap默认负载因子,负载因子越小,hash冲突机率越低,综合结论得出0.75最为合适
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    //4.记录当前容器实际大小
    static int size;
    
    @SuppressWarnings("unchecked")
    @Override
    public V put(K k, V v) {
        //1.判断容器是否为空为空则初始化。
        if (table == null) {
            table = new Node[DEFAULT_INITIAL_CAPACITY];
        }
        
        //如果size大于阈值则进行扩容
        if (size > DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR) {
            resize();
        }
        
        //2.计算出index角标
        int index = getIndex(k, DEFAULT_INITIAL_CAPACITY);
        
        //3.将k-v键值对放进相对应的角标,如果计算出角标相同则以链表的形势存放
        Node<K, V> node = table[index];
        if (node == null) {
            table[index] = new Node<>(k, v, null);
            size ++;
            return table[index].getValue();
        } else {
            Node<K, V> newNode = node;
            
            //循环遍历每个节点看看是否存在相同的key
            while (newNode != null) {
                //这里要用equals 和 == 因为key有可能是基本数据类型,也有可能是引用类型
                if (k.equals(newNode.getKey()) || k == newNode.getKey()) {
                    newNode.setValue(v);
                    size ++;
                    return v;
                } 
                newNode = node.getNextNode();
            }
            table[index] = new Node<K, V>(k, v, table[index]);
            size ++;
            
            return table[index].getValue();
        }
        
    }
    
    /**
     * 获取index
     * @param key
     * @param length
     * @return
     */
    public int getIndex(K key, int length) {
        int hashCode = key.hashCode();
        int index = hashCode % length;
        return index;
    }

    /**
     * 获取key
     */
    @Override
    public V get(K k) {
        int index = getIndex(k, DEFAULT_INITIAL_CAPACITY);
        Node<K, V> node = table[index];
        if (k.equals(node.getKey()) || k == node.getKey()) {
            return node.getValue();
        } else {
            Node<K, V> nextNode = node.getNextNode();
            while(nextNode != null) {
                if (k.equals(nextNode.getKey()) || k == nextNode.getKey()) {
                    return nextNode.getValue();
                }
            }
        }
        return null;
    }
    
    /**
     * 对size进行扩容
     */
    @SuppressWarnings("unchecked")
    public void resize() {
        //1.创建新的table长度扩展为以前的两倍
        int newLength = DEFAULT_INITIAL_CAPACITY * 2;
        Node<K, V>[] newtable = new Node[newLength];
        //2.将以前table中的取出,并重新计算index存入
        for (int i = 0; i < table.length; i++) {
            Node<K, V> oldtable = table[i];
            while (oldtable != null) {
                //将table[i]的位置赋值为空,
                table[i] = null;
                
                //方法1:重新计算index,然后按照put时候的方法进行放值,此种方法会不停的new 对象会造成效率比较低
                /*K key = oldtable.getKey();
                int index = getIndex(key, newLength);
                newtable[index] = new Node<K, V>(key, oldtable.getValue(), newtable[index]);
                oldtable = oldtable.getNextNode();*/
                
                //方法2:
                //计算新的index值
                K key = oldtable.getKey();
                int index = getIndex(key, newLength);
                
                //将以前的nextnode保存下来
                Node<K, V> nextNode = oldtable.getNextNode();
                
                //将newtable的值赋值在oldtable的nextnode上,如果以前是空,则nextnode也是空
                oldtable.setNextNode(newtable[index]);
                newtable[i] = oldtable;
                
                //将以前的nextcode赋值给oldtable以便继续遍历
                oldtable = nextNode;
            }
                
        }
        
        //3.将新的table赋值回老的table
        table = newtable;
        DEFAULT_INITIAL_CAPACITY = newLength;
        newtable = null;
        
    }

    @Override
    public int size() {
        return size;
    }

    @SuppressWarnings("hiding")
    class Node<K, V> implements Entry<K, V> {
        private K key;
        private V value;
        private Node<K, V> nextNode; //下一节点
        
        public Node(K key, V value, Node<K, V> nextNode) {
            super();
            this.key = key;
            this.value = value;
            this.nextNode = nextNode;
        }

        @Override
        public K getKey() {
            return this.key;
        }

        @Override
        public V getValue() {
            return this.value;
        }

        @Override
        public void setValue(V value) {
            this.value = value;
        }

        public Node<K, V> getNextNode() {
            return nextNode;
        }

        public void setNextNode(Node<K, V> nextNode) {
            this.nextNode = nextNode;
        }

        public void setKey(K key) {
            this.key = key;
        }
        
        //判断是否还有下一个节点
        /*private boolean hasNext() {
            return true;
        }*/
        
    }
    
    
    
    // 测试方法.打印所有的链表元素
    public void print() {
        for (int i = 0; i < table.length; i++) {
            Node<K, V> node = table[i];
            System.out.print("下标位置[" + i + "]");
            while (node != null) {
                System.out.print("[ key:" + node.getKey() + ",value:" + node.getValue() + "]");
                node = node.nextNode;
            }
            System.out.println();
        }

    }
    
}

2.测试代码

package com.test;

public class TestMap {
    public static void main(String[] args) {
        MyHashMap<String, String> extHashMap = new MyHashMap<String, String>();
        extHashMap.put("1号", "1号");// 0
        extHashMap.put("2号", "1号");// 1
        extHashMap.put("3号", "1号");// 2
        extHashMap.put("4号", "1号");// 3
        extHashMap.put("6号", "1号");// 4
        extHashMap.put("7号", "1号");
        extHashMap.put("14号", "1号");

        extHashMap.put("22号", "1号");
        extHashMap.put("26号", "1号");
        extHashMap.put("27号", "1号");
        extHashMap.put("28号", "1号");
        extHashMap.put("66号", "66");
        extHashMap.put("30号", "1号");
        System.out.println("扩容前数据....");
        extHashMap.print();
        System.out.println("扩容后数据....");
        extHashMap.put("31号", "1号");
        extHashMap.put("66号", "123466666");
        extHashMap.print();
        // 修改3号之后
        System.out.println(extHashMap.get("66号"));
        
    }
}

 

如有疑问或错误请在下方留言指出!  谢谢大佬

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

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

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

对HashMap的思考及手写实现

vue10行代码实现上拉翻页加载更多数据,纯手写js实现下拉刷新上拉翻页不引用任何第三方插件

纯手写springIOC

阿里面试官叫我手写HashMap,我两分钟就给他整出来了!!!