JDK7--HashMap源码分析(一)

    HashMap应该是在javaweb开发里面使用频率最高的几个类之一,使用频率直逼String类。因此也就有必要弄清楚这个类的实现原理,后面如果使用HashMap产生问题的时候分析原因也要从容一些 。

    HashMap内部的结构是数组+链表。数组存储着由hash值根据一定的算法计算出的有相同特征值的对象组成的链表。

  如下图:

         下图表示一个HashMap的结构,其中数组长度是M,虽然各个链表的长度不一定一样,为了方便,统一把长度表示为N。

         左侧是数组,存储链表的头部,右侧是所对应的链表的其它节点。

        数组存的是各个链表的头部,如果某个链表进来一个新对象,则它将取代原有的头部存储在数组中,原有的头部将后移一位。

 

    下面开始源码的分析:

    

    1.继承关系和实现的接口

          

    public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

         HashMap实现了Cloneable接口,HashMap对象可以调用clone()方法进行自我复制。

         HashMap还实现了Serialiable接口,这个是序列化接口,说明HashMap可以进行序列化,序列化后可以在网络上传输。

         当然了,HashMap实现了Map接口和继承了AbstractMap抽象类,使其具有Map类的基本功能。

    2.类属性

         

/**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient int modCount;

       DEFAULT_INITIAL_CAPACITY:Map默认的容量大小。

       MAXIMUM_CAPACITY:Map的最大的容量大小。

        DEFAULT_LOAD_FACTOR:默认阈值,如果当前HashMap里面的对象数量超过当前容量*当前阈值,则进行扩容。

        table:是个Entry数组,储存链表的头部。

        size:HashMap所储存的键值对数量。

        threshold:当前容量*阈值。

        loadFactor:当前阈值。

       modCount:用于记录HashMap的修改次数(这个后期再详细解释)

3.构造方法

    

/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    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);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }

     构造方法也比较直观,主要分两类:

                1.构造一个空的HashMap,重载的构造方法的参数主要是容量和阈值。

                2.用一个已有的HashMap对象生成一个新的对象。

4. 内部类

    a.Entry<K,V>

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;

      Entry是HashMap存储数据的核心,数据对象被加入HashMap中时,实际上是被封装到了Entry对象中。

  Entry对象里面不仅存储着对象的key和value还存储着指向链表下一个对象的值和该对象的hash值。

5.常用的方法

  a.get(Object key)

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

     get()方法的实现原理是:先根据对象的hashcode得到数据应该被存储的链表,然后遍历该链表获得value对象。

      方法内部还调用了几个方法:

       1.getForNullKey():如果可以为空的话,则遍历第一个链表,获取value值。

       2.hash()和indexFor()这两个方法一起确定了数据存储的链表。indexFor只有一行:

static int indexFor(int h, int length) {
        return h & (length-1);
    }
        这个与运算实际上等同于求余运算。即:hash()%size

  b.put(Object obj)

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

    put()方法实现原理:

         先判断key值是否为空,如果是空,则加在第一个链表的后面。否则,根据Hashcode计算hash值进而得到应当被放置的链表。

        遍历该链表,判断是否已经存在hash值和key值的都相同的Entry对象。如果存在则覆盖value值,并返回旧的value值,put()方法结束;如果不存在,则将Entry对象挂载到链表的上面。如果Map里面的键值对的数量超过了阈值,则对链表的数量也就是Entry数组的长度进行扩容,将数组的长度扩展为原来的两倍。然后,重新挂载原来的Entry对象,可能会更新其所在的链表。


        

        

        


发布了9 篇原创文章 · 获赞 0 · 访问量 7074

猜你喜欢

转载自blog.csdn.net/yangyingjieshiwo/article/details/80375258