`

HashMap源码解析

    博客分类:
  • JDK
阅读更多
HashMap源码解析
 public class HashMap<K,V> 
        extends AbstractMap<K,V>
           implements Map<K,V>, Cloneable, Serializable

HashMap是继承自AbstractMap<K,V>,那我们先来看一个AbstractMap<K,V>
   public abstract class AbstractMap<K,V> implements Map<K,V> {

AbstractMap<K,V>是接口Map<K,V>的实现,那我们看一个Map<K,V>
   public interface Map<K,V> {

       Map<K,V>定义了一系列方法的接口,AbstractMap<K,V>对Map<K,V>进行了方法的抽象,基本
   上实现在Map的方法,留了一个接口
public abstract Set<Entry<K,V>> entrySet();

       以下是子类的这现方法:
    /**
     * Returns a collection view of the mappings contained in this map. 
       Each
     * element in the returned collection is a <tt>Map.Entry</tt>.  The
     * collection is backed by the map, so changes to the map are 
       reflected in
     * the collection, and vice-versa.
     */
   public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es = entrySet;
        return (es != null ? es : (entrySet = (Set<Map.Entry<K,V>>) 
       (Set) new EntrySet()));
    }
   

     有人可能郁闷了,这怎么实现的?,我们来看一个EntrySet类
    private class EntrySet extends AbstractSet/*<Map.Entry<K,V>>*/ {
        public Iterator/*<Map.Entry<K,V>>*/ iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

这里使用了一个AbstractSet,抽象集合类型,使用Hashmap的属性来初始化Set中的四个方法,从而达到初始化entrySet的目的.
我们来看一个这个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;//table数据过去了
            int i = t.length;
            Entry<K,V> n = null;
            if (size != 0) {  
                while (i > 0 && (n = t[--i]) == null)
                    ;
            }
            next = n;
            index = i;
        } 
        public boolean hasNext() {
            return next != null;
        }
        

        Entry<K,V> nextEntry() { 
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null) 
                throw new NoSuchElementException();
                
            Entry<K,V> n = e.next; 
            Entry[] t = table;
            int i = index; 
            while (n == null && i > 0)
                n = t[--i]; 
            index = i;
            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;
        }

    }

这个遍历器实现了hasNext(),Hashmap下一个元素是否为空,nextEntry()为Itarator的next()方法.
这HashIterator()方法初始化好了第一个元素,从HashMap的最后一个数开始查找,只到第一个不为空的元素,设置为next
此下标的设置为index,在调用next()时,首先把next附值给e,然后再调用e.next,如果e.next不为空,则下一个元素为同一木桶链,
如果为空,则查找数组table中的下一个元素.然后把当前元互设置为e,下一个元素设置为e.next(table[index]).这里还重写了Itrator的
remove()方法,移除当前key实体.
 /**
     * Removes and returns the entry associated with the specified key
     * in the HashMap.  Returns null if the HashMap contains no mapping
     * for this key.
     */
    Entry<K,V> removeEntryForKey(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            if (e.hash == hash && eq(k, e.key)) {
                modCount++;
                size--;
                if (prev == e) 
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
   
        return e;
    }

这是移除一个木桶的方法,如果当前待移除的木桶对象和当前待移除的木桶的上一个元素相同,则移走这个木桶,当前下标为下一个元素的对象,则当前元素的和下一个元素不相同,则当前的下一个为下一个元素.当前素和下一个元素相同的情况只有在一级木桶的时候出现.
transient Entry[] table;

有人称table对象为一个视图,我称之为木桶,如下图:



蓝色框里的为一级木桶,下面的为木桶链.其实HashMap使用的就是这种原理实现的.
知道了HashMap是如何实例化实体对象后,以下我来介绍一个HashMap是如何put,get相对就应的实体对象.
    public V put(K key, V value) {
	K k = maskNull(key); 
        int hash = hash(k);
        int i = indexFor(hash, table.length);
 
 

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

这是put方法,很简单,首先通过你put进来的对象的hashCode()(经过位运算)和table对象的大小进行位&运算得到一个0到table.length的下标,如果此下标有值,并且key也相同,则发生替代行为,如果此下标没值,或者有值,则添加这个实体对象.
    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);
    }

这里添加这个实体对象,如果大小不够的话,会自动添加Hashmap的大小.这里的bucketIndex是一个木桶,可能只有一个也可能是一个链.
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        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();
        }
 
        void recordAccess(HashMap<K,V> m) {
        }
 
        void recordRemoval(HashMap<K,V> m) {
        }
    }

这是实体对象,本身就有一个next指向实体对象本身,这就形成了一个木桶链.
以上是put方法,下面介绍一个get方法:
    public V get(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry<K,V> e = table[i];//得到Entry.
        while (true) {
            if (e == null)
                return null;
            if (e.hash == hash && eq(k, e.key)) 
                return e.value;
            e = e.next;
        }
    }

通过key的hashCode()换算,直接到得对象在table[]中的index.得到对象后,循环得到这个对象链直到e.hash=hash,eq(k,e.key).然后返回这个值,如果找到最后都没有则返回null.
HashMap之后以速度会快,是因为对象的HashCode()都是唯一的,经过优化的hash算法可以实现对象链的减少,能够快速定位到对象本身[数组下标].
2010-10-14 日添加说明如下:
HashMap:
其实现方式
transient Entry[] table;
采用实体数组的方式存储,以一个hashCode[经过换算后的数组下标]
具体算法如下:
int hash = hash(key.hashCode());
static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
        return h & (length-1);
}
这里先根据对象本身的hashCode()方法,然后再移位操作,最后执行与操作,生成数组下标。
有人会问了,原来HashMap就是一个数组啊,对了,就是数组,只不过他在数组的级别上稍微做了改变,比如其下标的生成方式,还有一点也是HashMap特有的,散列表:
如果数组下标相同,那么这个数组的值就会被重写,HashMap也一样:
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;
            }
}
这里的e.value = value就是把原先的值改为新的值,这里的e是对象的引用.Entry是一个Map的内部封装类型,其作为对象存储在table的值中.
接下来当hash值在同一个范围,key不同时,那么生成的下标原先存在,而值却不同,接下来就看HashMap的散列表:
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);
}
如果pucketIndex[根据hash生成的数组下标]在table中不存在,那么e就为空,也就是next为空。Table[pucketIndex]就为当前对象。如果不为空,那么原先存在于这个下标下的元素就成为当前的next,这个列表可能会不停的加长,10个,20个….
如果你的HashMap效率很低,那么你要考虑是不是对象的HashCode()方法有问题.

  • 大小: 34.1 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics