1.简介
通过上面的一篇随笔我们知道了HashSet的底层是采用Map实现的,那么Map是什么?它的底层又是如何实现的呢?这下我们来分析下源码,看看具体的结构与实现。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。Map.Entry是其的内部类,描述Map中的按键/数值对。需要指出的是 Map,允许null的键也允许null的值。它的实现主要有HashMap和sortedMap,其中SortedMap扩展了Map使按键保持升序排列,下面我们简要分析下HashMap的具体实现。首先给出一个应用举例:
代码如下 | 复制代码 |
package com.test.collections;
|
2.继承结构
HashMap直接继承了AbstractMap类,实现了Map
DEFAULT_INITIAL_CAPACITY:默认的初始化容量(16);
MAXIMUM_CAPACITY:能够允许的最大容量(1 << 30);
DEFAULT_LOAD_FACTOR:缺省的加载因子(0.75);
Entry[] table:存储具体的值。
transient int size:记录Map的大小。
final float loadFactor:加载因子。
上面的属性中除了一个Entry类型,这个是什么意思呢。原来这里面就是维护Map键值的类,用来存储Map的具体值的,让我们来看下它的具体实现结构:
代码如下 | 复制代码 |
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** public final K getKey() { public final V getValue() { public final V setValue(V newValue) { public final boolean equals(Object o) { public final int hashCode() { public final String toString() { /** /** |
Entry
3.源码解析
a:构造函数
代码如下 | 复制代码 |
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity this.loadFactor = loadFactor; public HashMap() { public HashMap(Map<? extends K, ? extends V> m) { void init() { |
构造方法只需要说明第一个的即可,其他的都是传递一些缺省的参数值然后调用第一个构造方法实现具体的操作。重点看下第一个构造方法。它首先判断一下传入的容量是否合法以及加载因子是否合法。如果容量操作最大值是需要将它重置的,但是如果传入的值为负数是要抛出异常的。然后根据容量与加载因子的乘积得出临界值并且赋值给属性threshold,然后通过 table = new Entry[capacity]分配存储空间,完成了构造过程。Init()方法为空,不知道做了什么事情。
2.hash(int h)
代码如下 | 复制代码 |
static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } |
根据Hash 值确定键的位置,如果传入的为null,那么返回的hash值就是为0,索引值就是0.
3.size(),isEmpty()
代码如下 | 复制代码 |
public int size() { return size; } /** * Returns <tt>true</tt> if this map contains no key-value mappings. * * @return <tt>true</tt> if this map contains no key-value mappings */ public boolean isEmpty() { return size == 0; } |
Map大小就是直接返回属性值size的值,判断是否为空就是如果size为0说明为空否则不为空。
4.get(Object)
代码如下 | 复制代码 |
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); 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.equals(k))) return e.value; } return null; } |
根据键返回对应的值,首先判断键是否为null,如果为空的话就调用getForNullKey()返回空键对应的值只会有一个。其实也就是返回Map的第一个值,因为null对应的hash值为0,存储位置就是第一个。然后调用hash()方法返回唯一对应的hash值,然后再循环遍历这个 Map,如果发现Hash值相等就直接返回它的值,如果没有发现对应的值就返回null.
5.containsKey(Object )
代码如下 | 复制代码 |
public boolean containsKey(Object key) { return getEntry(key) != null; } final Entry<K,V> getEntry(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); 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的方法是根据getKey()方法来实现的。首先获取Hash值,然后遍历循环这个Entry数组,如果遇到键相同就返回否则就返回为null.
6.put(K,V)
代码如下 | 复制代码 |
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); 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++; addEntry(hash, key, value, i); return null; } 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); } void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); } |
我们是通过put(K,V)方法来添加对象到Map中的,首先判断传入的Key是否为null,如果为null就直接调用 putForNullKey(value)方法将null的键对应上值。如果不为空就首先获取K对应的Hash值,然后遍历循环这个Map,如果值已经存在就更新覆盖value并且返回返回老的value,否则的话就调用addEntry(hash, key, value, i);插入新值。插入及很简单了,New一个Entry对象然后确定数组位置指定值即可。
4.其他
HashMap的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量是哈希表中数据的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的数目。