论坛首页 Java企业应用论坛

说一说java里面的hashcode4-hashmap代码分析1

浏览 1037 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (11)
作者 正文
   发表时间:2012-02-23  
http://www.hetaoblog.com/%E8%AF%B4%E4%B8%80%E8%AF%B4java%E9%87%8C%E9%9D%A2%E7%9A%84hashcode4-hashmap%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%901/
现在进入hashcode第4篇,HashMap代码分析,
先看到这个类的成员变量,有下面8个,
static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认的容量
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量, 取到2^30
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认的load factor,
transient Entry[] table; // 这里,用一个数组存储了真正的Entry,Entry里面有成员k和v
transient int size; // 大小
int threshold; // 容量限制
final float loadFactor; // 实际load factor
transient volatile int modCount;

重点:
* 实际的kv数据用一个entry类,存放在一个数组里面
下面看下默认的构造函数

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 构造Entry表格
        init(); // 这个函数是空的,可以忽略
    }

下面是put函数,这段函数的主要意思:
1. 因为HashMap允许null(而HashTable不允许), 所以如果是null的话先固定放到第一个
2. 对对象的hashcode()再做一次hash,然后取索引,这里略有点故事,一般hash表的数组长度应该是质数,而jdk里面的HashMap选了2的指数作为数组大小,这里要做一次保护,这个下次可以单独写一点
3. 接下来,取到这个哈希数组里面的索引这项,然后对这项上的链表进行遍历,看看有没有存在一个key,和新放进来的元素hash值一样,并且(是同一个元素或者equals方法也返回相等)(这就是为什么Object.hashcode()里面会有那样的约定,如果你违法了那边的约定,假设两个对象equals返回true,本质上是同一个对象,但是hashcode不一样的话,那两次put会放到两个不同的地方了;
如果有的话,说明是同一个key对象,用新的value替换老的value,并且返回老的value
4. 如果不存在同一个key,那么就把该元素加入到这个哈希数组的这个位置(就是通过hashcode()再做hash,然后取索引的这项)

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

这里具体的addEntry代码如下:

    void addEntry(int hash, K key, V value, int bucketIndex) {
Entry e = table[bucketIndex];
        table[bucketIndex] = new Entry(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

以及Entry的实现和构造函数:

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

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

可以看到,
1. 新添加的元素被放到了哈希数组这个位置上面的链表的第一个;
2. 如果HashMap里面存放的总的元素个数超过了threshold(也就是capacity*loadfactor), 那么就把存放数据的数组扩大一倍
这里面是说总的元素个数,下面的代码是会打印2的。其实就是已经存了比较多的数据了,基本上直接hash一次访问到的概率比较小了,很可能很多元素都要继续通过访问后面的Entry链表来得到,效率就下降了,Hash的优势已经不那么明显了,所以把表格扩大一倍,把原来的元素根据hash值重新做一次索引和分布,尽可能让链表缩短,提高访问效率

Map m = new HashMap();

m.put("BB", 10);
m.put("Aa", 11);

System.out.println(m.size());
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics