`

Java集合类之HashMap源码分析

阅读更多

    hash表是一种常见的数据结构,主要是通过hash算法将数据尽可能的散列开来存放,当要查找某一数据时,可以通过hash算法直接定位,节省了对比查找的时间。map是一种key、value形式的键值对,将hash表和map结合即形成了HashMap。

    在Java中HashMap的数据是以Entry<key,value>数组的形式存放的,HashMap通过对key进行hash运算得到一个数组下标,然后将数据存放到Entry<key,value>数组对应的位置,又因为不同的key进行hash运算可能会得到一个相同的数组下标,为了解决碰撞覆盖冲突,所以Entry<key,value>本身又是一个链表的结构,即以后不同的key相同数组下标的数据的next会被赋值为已存在Entry<key,value>链表,新的Entry会替换数组值。

HashMap的存储数据的示例图如下:


 

 

HashMap 的put方法的源码解析

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value); // HashMap接收key为null的数据
        int hash = hash(key.hashCode()); //对key的hashCode再进行hash运算
        int i = indexFor(hash, table.length);//根据hash值和entry数组的大小计算出新增数据应该存放的数组位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
             // for循环遍历找到的数组下标的entry,如果hash值和key都相等,则覆盖原来的value值        
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        //如果上面for循环没有找到相同的hash和key,则增加一个entry
        addEntry(hash, key, value, i);
        return null;
    }

 

void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex]; //找到下标的entry
        //new 一个新的entry,赋值给当前下标数组
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

 

 Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n; //即将原来数组下标对应的entry赋值给新的entry的next
            key = k;
            hash = h;
        }

    (1)hash值相同且key相等数据将被覆盖。

    (2)添加新的entry时,将已存在的数据下标的entry(可能是null)赋值给新entry的next,新entry将替换原数组下标的值。

 

HashMap的get方法源码解析

    public V get(Object key) {
        //key为null时特别处理
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        //indexFor(hash, table.length) 根据hash值和数组长度计算出下标,然后遍历Entry链表
        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;
    }

 

总结

  • 一个对象当HashMap的key时,必须覆盖hashCode()和equals()方法,hashCode()的返回值尽可能的分散。
  • 当HashMap的entry的数组足够大,key的hash值足够分散时,即是可以实现一个entry数组下标最多只对应了一个entry,此时get方法的时间复杂度可以达到O(1)。
  • 在数组长度和get方法的速度上要达到一个平衡。数组比较长碰撞出现的概率就比较小,所以get方法获取值时就比较快,但浪费了比较多的空间;当数组长度没有冗余时,碰撞出现的概率比较大,虽然节省了空间,但会牺牲get方法的时间。
  • HashMap有默认的装载因子loadFactor=0.75,默认的entry数组的长度为16。装载因子的意义在于使得entry数组有冗余,默认即允许25%的冗余,当HashMap的数据的个数超过12(16*0.75)时即会对entry数组进行第一次扩容,后面的再次扩容依次类推。
  • HashMap每次扩容一倍,resize时会将已存在的值从新进行数组下标的计算,这个是比较浪费时间的。在平时使用中,如果能估计出大概的HashMap的容量,可以合理的设置装载因子loadFactor和entry数组初始长度即可以避免resize操作,提高put的效率。
  • HashMap不是线程安全的,多线程环境下可以使用Hashtable或ConcurrentHashMap。

 

  • 大小: 23.6 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics