HashMap各个版本分析


从Java1.6开始:
网络上分析HashMap的版本也主要是JDK1.6,
我的是java1.6.0_45

JAVA6

HashMap有3个构造方法

无参构造
public HashMap() {}

设置the initial capacity的构造方法
public HashMap(int initialCapacity) {}

设置the initial capacity和the load factor的构造方法
public HashMap(int initialCapacity, float loadFactor) {}

通过HashMap构造器放入一个HashMap直接初始化。
public HashMap(Map


 if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

 //MAXIMUM_CAPACITY MAXIMUM_CAPACITY = 1 << 30 最大容量 结果为 1073741824

 while (capacity < initialCapacity)
            capacity <<= 1;

HashMap的本质是 数组+链表

//数组原因 :
//源码是在初始化创建时:

//transient关键字标记的成员变量不参与序列化过程
transient Entry[] table;

table = new Entry[capacity];

//链表原因:
//Entry:
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

        /** * Creates new entry. */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
}

这里写图片描述

这个就是HashMap的结构

下面分析put方法

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        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))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

先分析 key==null的情况,HashMap中允许 key==null

private V putForNullKey(V value) {


//从这里可以了解到 null是存放在 table[0]中的,也就是第一行数组中
        //如果存在e.key==null的这种情况
        //重写value 否则->
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //<-来到这里
        //modCount字面意思就是修改次数
        //用modCount记录修改次数
        modCount++;
        //添加Entry,那么看下addEntry
        //了解到key=null的hash值为0,bucketIndex=0
        addEntry(0, null, value, 0);
        return null;
    }

Entry方法:

  void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    //对象集合默认初始化为null

    //所以从这里可以看出它是前插法
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //如果大小超过需要resize,这个后面讲.
        if (size++ >= threshold)
            resize(2 * table.length);
    }

此时再画一张图:

这里写图片描述

注意空字符串的hashCode为0,所以当key=”“的时候 key.hash也为0,

问当hash为0时.

 static int indexFor(int h, int length) {
        return h & (length-1);
    }

可知 hash值为0时,indexFor的返回值为0

他就放在bucketIndex为0,在Entry[0]里。

那么再看看删除的情况吧!
remove(Object)方法

 public V remove(Object key) {
        //hashMap.remove本质是 removeEntryForKey方法
        //那么紧接着看removeEntryForKey
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

removeEntryForKey:

//没加修饰符就是friendly。friendly只是一种说法,把它认为是defaul
//这里为了方便变色 写成protected
protected final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        //以上就是 1.先找到hash值 2.通过hash找到bucketIndex 3.通过bucketIndex找到Entry链
        while (e != null) {
        //其本质是能否找到e,如果能找到e
            Entry<K,V> next = e.next;
            //指针向后移动
            Object k;

            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                //k=e.key
                modCount++;
                size--;
                //删除就是删除当前节点。链表删除。
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                //删除返回
                e.recordRemoval(this);
                //空方法
                return e;
            }
            prev = e;
            e = next;
        }
        //找不到的话很明显 e=null
        return e;
    }

有些版本上说的是对: h%length 原因如下:
当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。

提高复用性,和解耦的关系

代码的封装是为了提高复用性

解耦是为了提高复用性

Java1.7

table表的建立转到了put方法中,先判断table是否存在,不存在创建

Java1.8

增加putVal方法,在putVal中判断table是否存在,通过resize方法进行初始化表
将新增数据的节点插入到尾部

Resize()

关于HashMap扩容是,Bucket扩容为原来的2倍, bucket<<=1;

HashMap的最大容量

//MAXIMUM_CAPACITY==1<<30
//Integer.MAX_VALUE==1<<31-1
//目的为了防止溢出
 if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
}

HashMap的容量是不小于输入数的1<<N;

Capacity指的是桶的大小,即纵向大小,并不是说总的大小。

Hash()作用是

它的目的是让“1”变的均匀一点,散列的本意就是要尽量均匀分

当计算出来的hash函数h和hashMap的length做了&运算后,会得到[0,length-1]其中的一个值,而散列的均匀也会使这个值分布的均匀,从而达到HashMap高效的一点。

hash对一个对象的hashCode进行重新计算,而IndexFor生成这个对象的index。

hash值重新计算,是为了防止质量低下的hashCode()函数实现。在hashMap数组长度中长度是初始长度的2倍。通过右移造成地位的数据尽量的不同。

而 在计算index上使用的是h&(length-1)的方法。简单而效率高。

看了Java的代码,自己在设计hash函数的时候,就有选择了。尽量使用位运算符,少使用+-*/%的运算符,这样可以提高hash的效率

JAVA1.6的hash

    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);
    }

JAVA1.7的hash

    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // 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);
    }

JAVA1.8的hash

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

聊得时候步骤

1.就table创建区别
2.HashMap1.6底层结构 1.7和1.8底层结构
3.hash的作用,每个版本不同 hash的作用和hash的实际案例 用处
4.具体求桶
5.最大值和初始化值
6.前插和后插
7.putIfAbsent
8.TreeBin的使用
9.涉及到线程安全的包 此时聊到HashTable
10.什么情况下用到hashMap

好好学,好好看

学习还是需要看源码的,被面试打击的不轻

转载注意

转载自:http://blog.csdn.net/boom_man/article/details/78286610

智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告