愿你从根本上了解HashMap

想必对于一个Java开发程序员来说,HashMap用的不少了,当被人问起你了解HashMap的时候,也许你会说:HashMap是线程不安全的,HashMap采取K,V的形式存储,HashMap是高效,强一点的会说,HashMap通过一个Entry<k,v>的内部类实现的,是的,可你真的了解Map吗?你会说Map是通过数组实现的,你或者也会说是通过链表实现的?可它实际上是通过数组和链表实现的,就算你知道这一点,你又知道为什么采取数组和链表去实现的吗?




HashMap(HashMap继承了AbstractMap父类,而AbstractMap父类实现了Map接口。)

下面介绍Map的存储数据的核心内部类entry:

每一个entry是链表结构,为什么这样呢?因为但key计算的hash值一样的话,通过链表的方式来解决冲突。也就是如果计算的hash一样的话就陆续存储在第一值得下一个节点。这样一来,对于不同的hash值的entry链表就组成了一个entry数组,现在明白了hashMap是数组和链表实现的吧。

我们观察源码可以发现:

(注意jdk1.8的HashMap重写了,比1.6的多了尽2000行代码,笔者只是针对1.6的源码分析,而1.8的实现大有不同)

HashMap的基本属性:

    /** Map的初始容量 默认是16 以2的次幂扩增
	 *首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,
	 *这样便保证了散列的均匀,同时也提升了效率;
	 *其次,length为2的整数次幂的话,为偶数,
	 *这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,
	 *也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,
	 *而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,
	 *即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,
	 *因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。
     * 
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大的容量
     */
	 
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 装载因子,越大冲突概率越小,但查找效率越低
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 用于计算阈值 实际大小超过临界值时,会进行扩容threshold = 加载因子*容量
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 用于计算阈值
     */
    static final int UNTREEIFY_THRESHOLD = 6;

 
    static final int MIN_TREEIFY_CAPACITY = 64;



重要:内部类node实现了Map的entry接口:

    //实现了Map里的Entry这个接口
	static class Node<K,V> implements Map.Entry<K,V> {
        //哈希值
		final int hash;
        //键
		final K key;
        //键值
		V value;
		//节点,实际上也是Entry
        Node<K,V> next;
		//内部类 Entry的构造方法
        Node(int hash, K key, V value, Node<K,V> next) {
            
			this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }
		//得到hashCode
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
		// 判断两个Entry是否相等,若两个Entry的“key”和“value”都相等,则返回true。否则,返回false        
        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {//instanceof关键字用于判断一个引用类型变量所指向的对象是否是一个类(或接口、抽象类、父类)的实例。
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }
	
	
而Map接口的entry是一个内部类(接口):

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

     
        V getValue();

       
        V setValue(V value);

        boolean equals(Object o);

        int hashCode();

       
        public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getKey().compareTo(c2.getKey());
        }

		
        public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> c1.getValue().compareTo(c2.getValue());
        }

        //返回一个以key自然排序
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
        }

       //返回一个以value的自然排序
        public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
            Objects.requireNonNull(cmp);
            return (Comparator<Map.Entry<K, V>> & Serializable)
                (c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
        }
    }
	
	
	

存储数据

public V put(K key, V value) {
     // 若“key为null”,则将该键值对添加到table[0]中。
         if (key == null) 
            return putForNullKey(value);
     // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
         int hash = hash(key.hashCode());
     //搜索指定hash值在对应table中的索引
         int i = indexFor(hash, table.length);
     // 循环遍历Entry数组,若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
         for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
             Object k;
              if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同则覆盖并返回旧值
                  V oldValue = e.value;
                 e.value = value;
                 e.recordAccess(this);
                 return oldValue;
              }
         }
     //修改次数+1
         modCount++;
     //将key-value添加到table[i]处
     addEntry(hash, key, value, i);
     return null;
}


 void addEntry(int hash, K key, V value, int bucketIndex) {
         Entry<K,V> e = table[bucketIndex]; //如果要加入的位置有值,将该位置原先的值设置为新entry的next,也就是新entry链表的下一个节点
         table[bucketIndex] = new Entry<>(hash, key, value, e);
         if (size++ >= threshold) //如果大于临界值就扩容
             resize(2 * table.length); //以2的倍数扩容
     }

	 
	 void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
       }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);//用来将原先table的元素全部移到newTable里面
        table = newTable;  //再将newTable赋值给table
        threshold = (int)(newCapacity * loadFactor);//重新计算临界值
    }
 读取数据

public V get(Object key) {   
    if (key == null)   
       return getForNullKey();   
    int hash = hash(key.hashCode());   
    for (Entry<K,V> e = table[indexFor(hash, table.length)];   
        e != null;   
        e = e.next) {   
        Object k;   
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))   
            return e.value;   
    }   
    return null;   
}

其他方法,计算hash ,计算索引

//计算hash值的方法 通过键的hashCode来计算
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

     static int indexFor(int h, int length) { //根据hash值和数组长度算出索引值
        return h & (length-1);  //这里不能随便算取,用hash&(length-1)是有原因的,这样可以确保算出来的索引是在数组大小范围内,不会超出
    }


HashMap允许key为null的真正原因:

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {   //如果有key为null的对象存在,则覆盖掉
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
           }
       }
        modCount++;
        addEntry(0, null, value, 0); //如果键为null的话,则hash值为0
        return null;
    }




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值