当初来公司时候,被问了一个问题,叫重写一个hashMap怎么写。数据结构捉鸡。。。所以,现在决定补上这东西。

  好啦,首先,先写点东西。

  HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的。 
  通过 HashMap、HashSet 的源代码分析其 Hash 存储机制
  实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素的存储位置,这样可以保证能快速存、取集合元素;对于 HashMap 而言,系统 key-value 当成一个整体进行处理,系统总是根据 Hash 算法来计算 key-value 的存储位置,这样可以保证能快速存、取 Map 的 key-value 对。
  在介绍集合存储之前需要指出一点:虽然集合号称存储的是 Java 对象,但实际上并不会真正将 Java 对象放入 Set 集合中,只是在 Set 集合中保留这些对象的引用而言。也就是说:Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的 Java 对象。 

一、从put分析table结构:

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

那我们发现,数据都是存放在table里,table是什么?table是一个Entry<K,V>[],键值对数组。存放方法如下:

  也就是说,用Entry,存放key、value、nextEntry,三个核心数据,就可以用table数组来实现hashMap。

二、大小、负载因子

  读jdk源码,我们发现HashMap这个类,有几个常量如下:

 /**
     * The default initial capacity - MUST be a power of two.默认初始容量
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 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;

  这就是我们说的容量和负载因子,什么意思呢?

  所谓容量,就是之前所说的「table」数组的大小,也就是说,HashMap初始默认的大小为16,一个size为16的Entry数组。而负载因子,决定table中装载Entry链的负载程度,默认就是16*0.75,一旦超过这个值,我们在put(k,v)中找到了方法  addEntry(hash, key, value, i),从中找到了方法resize():

void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

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

  一旦Map的size大于threshold(一般为容量*负载因子),则扩容2倍。

三、确定索引的算法

  好啦,那么put(k,v)是怎么决定index(table中的索引)的呢?

     int hash = hash(key);
        int i = indexFor(hash, table.length);

  那我们看到两个方法,来确定index,这两个分别长什么样子?hash算法是什么呢?

 /**
     * Retrieve object hash code and applies a supplemental hash function to the
     * result hash, which defends against poor quality hash functions.  This is
     * critical because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    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);
    }

    /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

  +_+,说到底,这个算法就是让得到的索引值总是位于 table 数组的索引之内。这个算法,实现的很“散”,突出了“散列表”的主题。

 

四、HashMap的使用

待续。。。