JCF之HashMap剖析

HashMap

HashMap继承了模板类AbstractMap。其内部数据结构域Hashtable相同:
 /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     * 长度必须是2的幂。原因后文会提高。
     */
 transient Entry[] table;//transient表示该数组不能被序列化,暂且不讨论序列化的问题
Entry<K,V>实现代码:
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;/此处用final修饰
        V value;
        final int hash;
        Entry<K,V> next;
 
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public K getKey() {
            return HashMap.<K>unmaskNull(key);
        }

        public V getValue() {
            return value;
        }
    
        public V setValue(V newValue) {
	    V oldValue = value;
            value = newValue;
            return oldValue;
        }
    
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2))) 
                    return true;
            }
            return false;
        }
    
        public int hashCode() {
            return (key==NULL_KEY ? 0 : key.hashCode()) ^
                   (value==null   ? 0 : value.hashCode());
        }
    
        public String toString() {
            return getKey() + "=" + getValue();
        }
    }

这是HashMap的一个内部类,它实现了Map类中的一个Entry接口,可以看出,HashMapHashtable的内部数据结构形式是相同的。

static   final   int   DEFAULT_INITIAL_CAPACITY  = 16;//HashMap 的默认大小为 16 ,而 Hashtable 的为 11

下面两个函数是Hashtable中所没有的:

  //如果key为null,则将其设为NULL_KEY,而NULL_KEY 为一个Object对象
    static <T> T maskNull(T key) {
        return key == null ? (T)NULL_KEY : key;
    }
  //如果key为NULL_KEY,则将其设为null
    static <T> T unmaskNull(T key) {
        return (key == NULL_KEY ? null : key);
    }
static final Object NULL_KEY = new Object();

这两个函数处理keynull的情况,如果为Null,则设置key为一个Object对象,这个对象是静态的,

且为final.,这表示如果有多个HashMap实例,那么所有的null key都会被设置成NULL_KEY

这两个函数的存在意味着HashMap中是允许Null key 的。

下面来看看HashMap put方法,整体思路与Hashtable是相同的,但实现有些不同:

public V put(K key, V value) {
   //处理key为null的情况,处理方式前面已经提到过
	K k = maskNull(key);
     //这是一个很大的不同:hash算法的不同
        int hash = hash(k);
        int i = indexFor(hash, table.length);//根据Hash值得到索引
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            if (e.hash == hash && eq(k, e.key)) {
                 //如果是同一个键,则用新值替代旧值
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(hash, k, value, i);
        return null;
    }
 
 static int indexFor(int h, int length) {
        //hash值与HashMap长度进行“与”运算,与Hashtable中的模运算不同
        return h & (length-1);
    }
//鉴于HashMap的长度是2的指数,为了提高Hash质量,设计了这个补充Hash函数
//( The shift distances in this function were chosen as the result
// of an automated search over the entire four-dimensional search space.
static int hash(Object x) {
        //先得到x的hashCode
        int h = x.hashCode();
          //h+(h左移9位之后取反的结果)
        h += ~(h << 9);
          //h异或(h进行无符号右移14位之后的结果)
        h ^=  (h >>> 14);
        //h+(h左移4位之后的结果)
        h +=  (h << 4);
          //h异或(h进行无符号右移10位之后的结果)
        h ^=  (h >>> 10);
        return h;
    }
 void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
 }

关于put方法的详细使用,Hashtable中分析了一个例子。

至于这个hash()函数为什么要这样设计,为什么移位数设置为9、14、4、10?

Goole到一个帖子,一哥们在想这个问题,他给HashMap的author发邮件询问,得到的回复是这样的:

I got really curious about those numbers. 9, 14, 4 & 10. What were the basis for those numbers?

 So not being able to contain my curiosity I wrote to Doug Lea and Doug kindly replied and his reply is...

"...The best answer is to read Donald Knuth's Art of Computer
Programming sections on hash tables where it explains
why, how, and when using power-of-two sizes works best.
In general, you need to use pre-conditioning functions
(as seen HashMap.hash) to make them work well, but given
these, they are faster than prime-number techniques...
"

大意是:Knuth的《The Art of Computer Programming》中关于hash tables的章节对将容器大小设为2的幂次效率更高作出了解释,

而为了获得这样的效率,需要做一些前期工作(像hash函数中那样)。

帖子链接:点击打开链接

这个问题先放一放,有时间再来看。


总结一下HashtableHashMap的主要不同点:

1.Hashtable出现在Java1.0中,HashMap是随着Java1.2出现的,Hashtable版本要老

1.Hashtable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap,这个区别就像VectorArrayList一样

2.Hashtable不允许null(keyvalue都不可以),HashMap允许null(keyvalue都可以)

3.HashTablehash数组默认大小是11,增加的方式是 old*2+1HashMaphash数组的默认大小是16,增加的方式是 old*2

4.哈希值的使用不同,HashTable直接使用对象的hashCode而HashMap通过hash方法得到hashCode

HashMap应该是设计得更为合理,效率更高(从hash函数中可以看出)的哈希表,除了线程安全之外,Hashtable与HashMao相比,似乎没有优势。
而线程安全可以通过Collections.synchronizedMap 来实现。

HashSet

由于HashSet与HashMap关系非常,这里一并学习了。
HashSet的内部数据结构声明:

private transient HashMap<E,Object> map;

其数据结构居然是一个HashMap。到底是个啥子情况,来一看究竟。

 public int size() {
	return map.size();
  }
public boolean isEmpty() {
	return map.isEmpty();
 }
//获得HashSet的迭代器对象
public Iterator<E> iterator() {
       //返回KeySet的迭代器
	return map.keySet().iterator();
 }
//调用map的put方法,键值对的键作为set值,而value值为PRESENT,可知PRESENT为一个静态Object对象
 public boolean add(E o) {
	return map.put(o, PRESENT)==null;
 }
 private static final Object PRESENT = new Object();
从这段实现代码中可以看出,HashSet使用HashMap作为其底层数据结构,其值存储于<Key,Value>的Key中,而value则都是

一个静态Object对象。HashSet通过HashMap的keySet来遍历元素。接下来一揭keySet的真面目。

一下是HashMap中keySet的实现代码:

private class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();//看来需要看看newKeyIterator();

        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
Iterator<K> newKeyIterator()   {
        return new KeyIterator();//继续,需要找到KeyIterator()
    }
private class KeyIterator extends HashIterator<K> {//继续,找到HashIterator
        public K next() {
            return nextEntry().getKey();
        }
    }

//Bingo,抽象类HashIterator,实现了Iterator接口

private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;	// next entry to return
        int expectedModCount;	// For fast-fail 
        int index;		// current slot 
        Entry<K,V> current;	// current entry

        HashIterator() {
            expectedModCount = modCount;
            Entry[] t = table;//当前HashMap的table
            int i = t.length; //注意,这里i是table的长度,而不是HashMap中的元素个数
            Entry<K,V> n = null;
            if (size != 0) { 
                while (i > 0 && (n = t[--i]) == null)//i>0且t[i]==null;从table尾部开始,找到第一个不为Null的位置
                    ;
            }
            next = n;//next此时指向从table尾部开始向前第一个不为Null的位置,index为该位置索引
            index = i;
        }

        //如果next为null,说明table的第一个位置为Null(即table[0]==null),显然此时为空
        public boolean hasNext() {
            return next != null;
        }
      
         //从下面这个函数中可以看出nextEntry()是从table尾部向前遍历的,如遇hash冲突链,则会冲链头开始遍历
        Entry<K,V> nextEntry() { 
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;//用e指向table中从尾部开始向前第一个不为null的位置

            if (e == null) 
                throw new NoSuchElementException();
                
            Entry<K,V> n = e.next;//如果e存在hash冲突链表,则n不为Null
            Entry[] t = table;
            int i = index;
             //如果n为null,即该处不存在hash冲突链表,则table继续向前
             //那么如果n不为Null,则它此时是指向冲突链表的下一个位置
            while (n == null && i > 0)
                n = t[--i];
            index = i;//如果n不为Null,则索引位置将不发生变化
            next = n; 
            return current = e;
        }
        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

至此,HashSet的实现已经了然了。。



  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值