手写HashMap

本文基于JDK1.7

虽然很早之前看过hashMap源码,但是还是需要实战一下,手写了一个类似的HashMap,加深对集合类的理解。代码我加上了详细注释。

问题:

  1. 如何将元素放入数组对应位置? 即寻址过程
  2. 不同元素在数组位置相同,发生冲突如何解决?
  3. 数组容量不足,怎么扩容?

搞清楚这三个问题,我们就可以很轻松完成自己的HashMap:
自己写的Map,就简单点,不需要像源码那样考虑健壮性,只需要弄清大致流程即可。

先定义一个SelfMap抽象接口:

public interface SelfMap<K, V> {

    V put(K k, V v);

    V get(K k);

    int size();

    interface Entry<K,V> {
        K getKey();

        V getValue();

        V setValue(V value);
    }

}

Node节点是存放在数组和链表的元素,是一个键值对。

下面开始实现上面Map:
定义常量:

	//定义数组默认容量为16
    private int capacity = 1 << 4;

    //数组实际元素数量
    private int size = 0;
	
	//负载因子
    private float loadFactor = 0.5f;

   	//数组,初始默认为空,第一次add时创建
    Node<K, V>[] table = null;

定义四个常量,其中数组我们使用lazyInit,第一次添加的时候初始化数组

具体方法实现
需要弄清楚以下问题:

  1. 第一次放数据初始化数组
  2. 判断是否需要扩容
  3. 键为null需要放到数组index=0的位置
  4. 计算Index = hash & (array.length - 1)
  5. 何时put直接覆盖value(相同对象)?
  6. hash冲突使用头插法构造链表
  7. 扩容后同一链表元素在新数组位置相同吗?链表元素顺序还能保持吗?
	@Override
    public V put(K k, V v) {

        // 判断table 数组大小是否为空(如果为空的情况下 ,做初始化操作)
        if (table == null) {
            //初始化数组 懒加载
            table = new Node[capacity];
        }

        //首先判断是否需要扩容
        if(size > loadFactor * capacity) {
            resize();
        }

        // if k=null 需要插入到0位置(针对键为null情况)
        if(k == null) {
            //初始化Node
            table = new Node[capacity];
            Node<K, V> node = table[0];
            //0位置本身有值,替换value
            if(node != null) {
               return node.setValue(v);
            }
            //0位置为空,直接放入
            node = new Node<K, V>(null, v, null);
        }

        // 计算index
        int index = getIndex(k, capacity);
        //定位index位置元素
        Node<K, V> node = table[index];
        Node<K, V> newNode = node;

        // 判断index是否有值(即是否发生Hash冲突),不冲突的话直接放入
        if(node == null) {
            //构建新节点直接放入
            node = new Node<K, V>(k, v, null);
            size++;
        }

        //冲突,使用头插法
        while (newNode != null) {
            //不为空先判断是否为同一对象
            if(newNode.getKey() == k || newNode.getKey().equals(k))  {  //保证hashCode相等,equals相等,但是不一定是一个对象,比如两个String和类型数据,覆盖value
                return newNode.setValue(v);
            }
            //遍历到最后一个节点,没有发现相同节点,此时需要插入到头结点
            if(newNode.next == null) {
                node = new Node<>(k, v, node);
            }
            newNode = newNode.next;
        }

        table[index] = node;
        return null;
    }
    
	/**
	* 获取元素
	**/
    @Override
    public V get(K k) {
        if(k == null) {
            //判断map集合是否为空
            if(size == 0) {
                return null;
            }
            return table[0].getValue();
        }

        int index = getIndex(k,table.length);
        return table[index] == null? null : table[index].getValue();
    }

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

    /**
     * 扩容方法
     */
    public void resize() {
        //数组长度变为原来的两倍
        Node<K, V>[] newTable = new Node[capacity << 1];
        
        //重新计算index
        for (int i = 0; i < table.length; i++) {
            Node<K, V> e = table[i];
            while (e != null) {
                //重新计算index
                int position = getIndex(e.getKey(), table.length << 1);
                Node<K, V> next = e.next;
                //让当前节点的下一节点指向新数组指定位置链表的头
                e.next = newTable[position];
                //头插法插入
                newTable[position] = e;
                //移动到链表的下一位置
                e = next;
            }
        }
        table = newTable;
        capacity = newTable.length;
        //去掉引用,方便垃圾回收
        newTable = null;
    }


    /**
     * 定位元素数组位置
     * @param k
     * @param length
     * @return
     */
    public int getIndex(K k, int length) {
        int hashCode = k.hashCode();
        int index = hashCode & (length-1);
        return index;
    }

    class Node<K, V> implements Entry<K, V> {

        private K key;

        private V value;

        private Node<K, V> next;

        public Node(K key, V value, Node<K, V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

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

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

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

上面问题回答

  • 何时put直接覆盖value(相同对象)?

注意,并不是相同对象才可以,对于String类型,不同对象相同值也会覆盖。jdk中有一个IdentityHashMap是判断是否为同一对象进行覆盖的

  • 扩容后同一链表元素在新数组位置相同吗?链表元素顺序还能保持吗?

有可能相同也有可能不同,如果相同,生成的链表顺序会出现倒序,比如开始三个节点顺序为A->B->C,在新数组的顺序为C->B->A,但是在jdk1.8中顺序不会改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值