Java集合源码 -- Map映射和Set集合

Map接口

Map接口是有一个映射表, 存储键和值, 它提供了两个通用的接口HashMap 和 TreeMap

HashMap 是散列映射表, 对键散列; Tree是树映射表, 对键进行排序,并将其组织成搜索树

 

除了定义添加,删除, 视图等方法,还定义了一个子接口Entry, 用来操作键值对

HashMap

概述

HashMap是散列映射表,key-value总是会当做一个整体来处理,根据hash算法来来计算key-value的存储位置

主要属性

transient Entry<K,V>[] table;   //存储元素
transient int size;  //键值对的个数
int threshold;    //当元素个数超过这个域值后,就会自动扩展映射表的大小 
final float loadFactor;   //加载因子,表示threshold与table长度的比值

table是一个数组, Entry<K,V>表示一个键值对, Entry是Hash的内部类,定义如下

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;    //存储key
        V value;       //存储value
        Entry<K,V> next;  // 指向链表中下一个节点
        int hash;    //存储hash散列值

        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        ...........
}    

如何散列

Entry中存储了散列值,hashMap是如何散列的呢 ?

HashMap使用hash值确定一个Entry在table中的位置,h & (length-1)的结果,就是在table中的下标,

如果有多个hash在table中的同一个位置,那么就构成一个链表。如下图

添加

put(K key, V value):

public V put(K key, V value) {
         //如果table空,初始化
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //key为空,这也是允许key为null的原因
        if (key == null)
            return putForNullKey(value);
            
        //计算哈希    
        int hash = hash(key);
        //计算在table中的位置
        int i = indexFor(hash, table.length);
        
        //遍历链表,如果key相同, 新value覆盖老的, 并返回老value
        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++;
        
        //把Entry添加到链表中
        addEntry(hash, key, value, i);
        return null;
    }

如何扩容

在把Entry添加到链表的过程中,可能会有扩容的操作

 void addEntry(int hash, K key, V value, int bucketIndex) {
        //如果超出指定的容量上限,扩容为原来的两倍,并重新计算在table的位置
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);                      
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        //构造了一个Entry
        createEntry(hash, key, value, bucketIndex);
    }

扩容:

void resize(int newCapacity) {
        //保存原table
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        
        //判断原容量
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        
        //初始化一个table,新的容量
        Entry[] newTable = new Entry[newCapacity];
        
        //把原table的元素, 复制到新table
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        
        //新table覆盖原table
        table = newTable;
        
        //重新计算指定的容量上限
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

transfer(Entry[] newTable, boolean rehash): 复制原table元素到新table

 //把原table的元素, 复制到新table
      void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
    
        for (Entry<K,V> e : table) {     //遍历原table
            while(null != e) {        //遍历链表
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                //计算在新table中的位置
                int i = indexFor(e.hash, newCapacity);
               
               //原Entry的next置为空
                e.next = newTable[i];
                
                //原Entry复制给新Entry
                newTable[i] = e;
                
                //原Entry的next,指向老Entry
                e = next;
            }
        }
    }

 查找

getEntry(Object key): 根据key找节点,这个方法是基本操作方法, 其他的查找方法也是调用的这个方法

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        
        //遍历链表,hash相同并且key相同 , 返回节点
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

在取的过程中也是根据key的hashcode取出相对应的Entry对象。

 Set集合 -- HashSet 

Set接口是不允许元素重复的, HashSet是基于HashMap实现的

底层存储

private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

当添加一个元素时,它是把要添加的元素当做key, PRESENT 当做value 存入map中, 如下:

public boolean add(E e) {        
    return map.put(e, PRESENT)==null;    
}

它的其他方法也是调用的HashMap, 不再累述了

posted @ 2016-12-21 20:35  liuconglin  阅读(752)  评论(0编辑  收藏  举报