java核心之集合框架——HashMap源码分析

时间:2022-07-22 17:19:23

——每天的寥寥几笔,加持下去,将会是一份沉甸甸的积累。


源码分析第一篇先讲HashMap。

首先,明白HashMap分成Hash即hash表的数据结构,Map即Key-Value结构的值,然后就是将Key-value的值存到Hash表中。

OK,进入正题,

首先得有个Hash表的数据结构,在hashMap中是以链表数组实现的。

transient Entry<K,V>[] table;//Hash表,每个元素是Entry类型

static final int DEFAULT_INITIAL_CAPACITY = 16;//默认Hash表大小

public HashMap(int initialCapacity, float loadFactor) {
    this.loadFactor = loadFactor;//装填因子,如果Hash表的使用率超过装填因子时则需要扩容。
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    //当前最大可使用容量,等于装载因子乘以Hash表大小
    table = new Entry[capacity];//new出hash表,每个表元素又是一个链表结点

 static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;//可以看出这是一个链表结点,可以指向下一个元素
    int hash;
}

然后看来看看添加一个元素的put函数

public V put(K key, V value) {
	if (key == null)
	    return putForNullKey(value);//如果可key为null,则特殊处理,实际上就是下面当索引i=0的情况。因为null默认对应table[0]这个链表
	int hash = hash(key);//计算Hash值
	int i = indexFor(hash, table.length);//将计算出的Hash值压缩到0至table.length-1的表索引范围。
	//根据key计算出的索引,搜索对应的链表,如果找到相同key的则更新value,否则新建一个Entry链表结点。
	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;
	    }
	}
	//for循环中return没返回说明没有找到相同的key,下面新建结点
	modCount++;
	addEntry(hash, key, value, i);
	return null;
}
其中 putForNullKey():

    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {//遍历table[0]这个链表,找到相同key则更新value,否则新建一个结点
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

addEntry()和createEntry():

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
    	//表空间不足则扩容
        resize(2 * table.length);//两个操作,扩大表大小为之前的两倍,同时将原数据转移到新表,并将原引用指向新表。(resize函数和transfer函数就不贴了)
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }
    createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];//记录下索引为bucketIndex的链表首节点
    table[bucketIndex] = new Entry<>(hash, key, value, e);//创建一个新节点来作为首节点,并指向e这个原来的首节点
    size++;
}

hash函数(了解即可)和索引求解函数indexFor(很重要,需要理解):

final int hash(Object k) {//高级函数,大家了解即可
	int h = 0;
	if (useAltHashing) {
	    if (k instanceof String) {return sun.misc.Hashing.stringHash32((String) k);}
	    h = hashSeed;
	}
	h ^= k.hashCode();
	h ^= (h >>> 20) ^ (h >>> 12);
	return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
     return h & (length-1);//Hash表的长度要求是2的N次方,这样的话可以使得length-1的二进制码全为1,然后和h与操作后,结果将被限定在length-1的大小范围内
}

说完put(放),然后讲get(取)。

public V get(Object key) {
    if (key == null)
        return getForNullKey();//特殊处理,同上,遍历table[0]链表
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key);
    for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {//根据key计算table索引,然后遍历该链表,找到则返回,否则最后返回null
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))//比较是否相等,相等条件为Hash值相等并且key.equals(k)
            return e;
    }
    return null;
}
-----------------------------------------------------------------------重点理解------------------------------------------------------------------

接下来的重点是迭代器(很重要),遍历整个map(key,value,entry)的利器,不过Hashmap还是做了进一步的封装,将迭代器封装了相应的View视图中,三大视图keySet<k>,Values<v>,EnterSet<k,v>。


【View视图】——一个非常相当重要的概念,简单来说,原映射表通过一个方法来实例化view视图类并获得该对象,该对象实现了某些集合接口,并能对原映射表进行某些操作。举例来说,HashMap中有一个keySet方法,该方法返回一个内部类KeySet的实例对象,然后我们可以通过调用该对象的一些方法如get,set,remove等来对原映射表HashMap进行相关操作,而这些操作是能够影响原映射表HashMap的实例域的。说白了,就是原映射表提供出来了一个方法,让我们获得一个对象来操作原映射表。


首先为了获得Iterator接口类,先实现一个抽象类

private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;        // current的下一个的结点对象,即调用nextEntry方法返回的对象
        int expectedModCount;   // 期望的修改次数
        int index;              // 当前索引,指的是当前遍历的表是table[index]
        Entry<K,V> current;     // 当前的Entry结点。调用nextEntry方法后current=next

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // 指向第一个Entry next=t[0],准备开始遍历以next为首指针的链表
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }
        public final boolean hasNext() {
            return next != null;
        }
        final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;//调用nextEntry则把next赋给current(e = next;current = e;),返回该对象
            if (e == null)
                throw new NoSuchElementException();
            //判断是否要切到下一条链表
            if ((next = e.next) == null) {//e.next==null表明遍历完一条链表,接着切换到下一条链表,即next = t[index],index++
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            current = e;
            return 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);
            //本内部类本来仅仅是进行遍历、获取,如果要进行删除操作,必须改变外部类HashMap中的实例域,因此要调用外部类方法。
            expectedModCount = modCount;
        }
    }

上面这个是Iterator的抽象类,下面是三个实现了抽象类的子类并给与了三个获得子类实例对象的方法。

   private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }
    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }
    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }
    Iterator<K> newKeyIterator()   {//可以直接调用这三个方法来获得你想要的Iterator对象,通过Iterator对象来遍历原映射表
        return new KeyIterator();
    }
    Iterator<V> newValueIterator()   {
        return new ValueIterator();
    }
    Iterator<Map.Entry<K,V>> newEntryIterator()   {
        return new EntryIterator();
    }

但HashMap还是将上面的三个Iterator对象封装到了一个实现了某个集合接口的类中(这些类就是视图),这样就能更全面的对原映射表进行相关操作,或者限制相关操作(比如不添加add方法,那么获得的视图对象也将不能使用该方法来对原映射表操作,要想对原映射表进行该操作,必须通过其他方式直接获得原映射表的引用)
public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {//将Iterator对象封装进来,作为Set集合的一个子方法提供给用户使用,其实是等价于用newKeyIterator()
                                        方法获得,不过后者是包访问性,一般也无法访问得到,所以通过KeySet来访问Iterator对象
            return newKeyIterator();
        }
        public int size() {//提供了Set接口下其他一个实用的方法,使我们能够更好的操作原映射表
            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();
        }
    }

 public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

public Set<Map.Entry<K,V>> entrySet() {
        return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ? es : (entrySet = new EntrySet());
    }

    private final 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();
        }
    }


贴了这么多代码,这里在精炼一下,HashMap主要要理解如下几点:

1.内部数据结构是数组链表

2.了解从key到index的过程和原理(Hash,indexFor)

3.知道如何计算索引,然后简单的遍历链表(get,put)

4.视图的概念与使用(记住这东西就是封装出来供大家对原映射表进行相关操作的,如获得Iterator对象)