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, 不再累述了
祝:
大家生活愉快,工作顺利