JDK源码阅读笔记之——HashMap

一开始,注释说明就讲了一个我们经常问的问题:hashmap和hashtable有什么区别:

* the HashMap class is roughly equivalent toHashtable,exceptthat it is unsynchronized and permits nulls.

HashMap 和Hashtable基本上是一样的,但是HashMap不是线程安全,并且HashMap是允许null(key,value可以为null),反过来说,就是Hashtable是不能放null进去。


一、构造函数

HashMap有4个构造函数

HashMap()
Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75) .
构造一个初始容积为16,负荷率为0.75的空的HashMap
HashMap(int initialCapacity)
Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
构造一个自定义容积和负荷率为0.75的空的HashMap
HashMap(int initialCapacity, float loadFactor)
Constructs an empty HashMap with the specified initial capacity and load factor.
构造一个自定义容积和负荷率的空的HashMap
HashMap(Map<? extendsK,? extends V> m)
Constructs a new HashMap with the same mappings as the specified Map.
复制一个已有的Map

容量(capacity)很好理解,就是哈希表的大小,如果不知道什么是哈希表的,可以查一下相关资料,

负荷率(load factor)就是当一张表达到多少使用率的时候,会自动增加哈希表的长度,此时哈希表内部数据结构重建。


先看HashMap(int initialCapacity, float loadFactor)这个方法,主要就是初始化loadFactor(记录负荷率)和threshold(记录容量×负荷率

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }


然后再看一下HashMap(Map<? extends K,? extends V> m)

 public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

inflateTable后面讲put函数的时候再说,主要看一下putAllforCreate()

private void putAllForCreate(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            putForCreate(e.getKey(), e.getValue());
    }
</pre><pre name="code" class="java"><pre name="code" class="java"> private void putForCreate(K key, V value) {
        int hash = null == key ? 0 : hash(key);
        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 != null && key.equals(k)))) {
                e.value = value;
                return;
            }
        }

        createEntry(hash, key, value, i);
    }

其中,indexFor函数就是获取该哈希值在 table中的位置,具体可以看看 http://www.360doc.com/content/10/0505/19/495229_26234886.shtml,讲得比较详细

 
不过又可以得看出,如果map中传的一个引用类型,如果用上面的方法构造另外一个map,改变其中一个都会影响另外一个.

二、put()

先贴代码
 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
<span style="white-space:pre">	</span>//如果table[i]存在
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
<span style="white-space:pre">	</span>    //首先hash值相同,然后再比较是不是==或者equals,如果相同则<span style="font-size: 12px;">赋予</span>新的value值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
<span style="white-space:pre">	</span>//如果不存在,则在该位置增加一个Entry<K,V>
        addEntry(hash, key, value, i);
        return null;
    }


Entry<K,V>其实是一种链表形式,不过是用的头插法,即每次插入新的元素的时候,是在链表的第一个位置插入。 Entry<K,V>中有一个next属性用来记录下一个位置的Entry<K,V>,如果是第一次插入,则把next指向自己,这样,在判断是否是最后一个元素提供的方法,该next是否是自己

在addEntry()中,首先第一步是判断是不是需要扩容,即当前table使用量(用size来记录)是否>=threshold,如果满足则会调用resize()函数,resize()就是对已有的table进行扩容(扩大2倍),然后再对已有的元素重新分配

三、inflateTable(int p)

这个函数功能很简单,就是table进行初始化,生成一个不小于p的2的幂整数,但是里面有个方法值得仔细研究
 private static int roundUpToPowerOf2(int number) {
        // assert number >= 0 : "number must be non-negative";
        int rounded = number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (rounded = Integer.highestOneBit(number)) != 0
                    ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded
                    : 1;

        return rounded;
    }

这个函数用是来生成不小于一个数的 2的幂整数,其中有2个函数是比较经典的位运算
 一个是Integer.highestOneBit(),用来判断一个数的二进制最高位的出现1的所对应的十进制数,其实就是找该数的不大于他的一个2的幂整数,  
比如 不大于23的幂整数是 16 。
23对应的2进制是 ...0001 0111,  我们找出最高的一个1所对应的2进制 即 0001 0000 ,(可以看成把之后的1全部舍去)就是十进制的16
代码如下:
 public static int highestOneBit(int i) {
        // HD, Figure 3-1
        i |= (i >>  1);
        i |= (i >>  2);
        i |= (i >>  4);
        i |= (i >>  8);
        i |= (i >> 16);
        return i - (i >>> 1);
    }
可以看成,通过不断右移,把高位第一个出现1的位置后面的全部弄成1,然后再用这个数减去它右移一位的数

还有一个函数是Integer.bitCount() ,用来判断一个2进制的数中,1位出现的个数,这个算法用的是一种并行算法,这个算法我也看了好久才弄明白
在jdk中,实现的代码如下:
  public static int bitCount(int i) {
        // HD, Figure 5-2
        i = i - ((i >>> 1) & 0x55555555);
        i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
        i = (i + (i >>> 4)) & 0x0f0f0f0f;
        i = i + (i >>> 8);
        i = i + (i >>> 16);
        return i & 0x3f;
    }



其他的方法就比较简单了, get 函数就是找出key 的哈希值,再在哈希表里面取出对应的Entity(链表),再遍历链表寻找对应的value


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值