Map
先看一下jdk中map的定义
public interface Map<K,V>{
int size();
boolean isEmpty();
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
void putAll(Map<? extends K, ? extends V>m);
void clear();
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
interface Entry<K, V>{
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
}
boolean equals(Object o);
int hashCode();
}
可以看到Map并没有实现Collection接口,因为它可以保存两个属性值key-value,同时还有增删该查等基本操作,同时可以看见Map中还定义了一个用来表示键值对K-V的接口Entry。
HashMap的实现
HashMap的定义
public class HashMap<K,V> extends AbstractMap<K,v> implements Map<K, V>, Cloneable, Serializable
HashMap是继承了AbstractMap抽象类,实现了Map,Cloneable,Serializable接口。
底层存储
// 默认初始容量为16,必须为2的n次幂
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量为2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子为0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// Entry数组,长度必须为2的n次幂
transient Entry[] table;
// 已存储元素的数量
transient int size ;
// 下次扩容的临界值,size>=threshold就会扩容,threshold等于capacity*load factor
int threshold;
// 加载因子
final float loadFactor ;
可以看出HashMap底层使用Entry数组存储数据。同时定义了加载因子等参数。
Entry的定义
static class Entry<K, V> implements Map.Entry<K, V>{
final K key;//定义键值
V value; 定义值
Entry<K, V> next; //指向下一个节点
final int hash;
Entry(int h, K k, V v, Entry<K, V> n){
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey(){
return key;
}
public final V getValue(){
return value;
}
public final V setValue(V newValue){
V oldValue = value;
value = newValue;
return oldValue;
}
public final 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 final int hashCode() {
return (key ==null ? 0 : key.hashCode()) ^
( value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
// 当向HashMap中添加元素的时候调用这个方法,这里没有实现是供子类回调用
void recordAccess(HashMap<K,V> m) {
}
// 当从HashMap中删除元素的时候调动这个方法 ,这里没有实现是供子类回调用
void recordRemoval(HashMap<K,V> m) {
}
}
Entry是HashMap的内部类,它继承了Map的Entry接口,它定义了键值和下一个节点的引用以及hash值。可以很明确的看出来Entry是什么结构,它是单链表的一个节点。也就是说HashMap的底层结构是一个数组,而且数组的元素是一个单向链表
HashMap将所有的相同的散列值存储在一个链表中,也就是在一个链表内的元素的散列值是相同的。
构造方法
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
// 确保容量为2的n次幂,是capacity为大于initialCapacity的最小的2的n次幂
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
// 赋值加载因子
this.loadFactor = loadFactor;
// 赋值扩容临界值
threshold = (int)(capacity * loadFactor);
// 初始化hash表
table = new Entry[capacity];
init();
}
/*
构造一个使用默认容器变量(16)和默认加载因子的HashMap
*/
public HashMap(){
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
增加方法
public V put(K key, V value){
//如果key为null,调用方法处理
if(key == null)
return putForNullKey(value);
//使用key的hashCode计算相对应的hash值
int hash = hash(key.hashCode());
//通过hash值找到在数组中的位置
int i = indexFor(hash, table.length);
//取出index位置的链表,遍历链表查看是否存在相同的key
for(Entry<K,V> e = table[i]; e != null ; e = e.next){
Object k;
//通过对比hash值,key判断是否已经存在相同的key
if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
V oldValue = e.value;
//用新的value替代之前旧的value
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//若不存在相同的key
modCount++;
//在数组的第i个位置处添加一个新的节点
addEntry(hash, key, value, i);
//没有相同的key返回null
return null;
}
private V putForNullKey(V value){
//找到一个key为null的节点,覆盖,若没有,则新增一个
for(Entry<K,V> e = table[0]; e != null; e = e.next){
if(e.key == null){
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
增加和我们上面分析的一样,通过key做hash取得一个散列值,将散列值对应到数组下标,然后k-v组成链表节点添加到数组。
计算hash的方法和计算索引的方法
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power -of- two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
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);
}
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
增加节点的方法
/*
增加一个k-v,hash组成的节点在数组内,可能要对数组进行扩容
*/
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);
//如果当前的HashMap的值已经达到了临界值,
// 那么容量扩大两倍,并且将size计数+1
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)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable){
//当前数组
Entry[] src = table;
//新数组长度
int newCapacity = newTable.length;
// 遍历当前数组的元素,重新计算每个元素所在数组位置
for (int j = 0; j < src. length; j++) {
// 取出数组中的链表第一个节点
Entry<K,V> e = src[j];
if (e != null) {
// 将旧链表位置置空
src[j] = null;
// 循环链表,挨个将每个节点插入到新的数组位置中
do {
// 取出链表中的当前节点的下一个节点
Entry<K,V> next = e. next;
// 重新计算该链表在数组中的索引位置
int i = indexFor(e. hash, newCapacity);
// 将下一个节点指向newTable[i]
e. next = newTable[i];
// 将当前节点放置在newTable[i]位置
newTable[i] = e;
// 下一次循环
e = next;
} while (e != null);
}
}
}
删除
public V remove(Object key)
{
Entry<K, V> = removeEntryForKey(key);
return (e == null?null:e.va;ue);
}
/*
根绝key删除链表节点
*/
final Entry<K.V> removeEntryForKey(Object key){
//计算key的hash值
int hash = (key == null) ? 0 : hash(key.hashCode());
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;
Object k;
//如果hash值和key值都相等,则删除
if(e.hash == hash &&
(k = e.key) == key || (key != null && key.equals(k))){
modCount++;
size--;
//若第一个节点要删除的节点
if(pre == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;//返回被删除的节点
}
//保存当前节点为下次循环的上一个节点
prev = e;
//下次循环
e = next;
}
}
查找
public V get(Object key)
{
//若key == null
if(key == null)
return getForNUllkey();
//计算key对应的hash值
int hash = hash(key.hashCode());
//通过hash值找到key对应数组的索引位置,遍历该数组的链表
for(Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next){
Object k;
//如果hash值和key都相等,则认为相等
if(e.hash == hash &&
(k = e.key) == key || key.equals(k)){
return e.vaule;
}
return null;
}
private V getForNullKey() {
// 遍历数组第一个位置处的链表
for (Entry<K,V> e = table [0]; e != null; e = e. next) {
if (e.key == null)
return e.value ;
}
return null;
}
从删除和查找知道,我们只需要遍历hash值一样的数据,这样比全遍历效率高很多。
是否包含键
/**
* Returns <tt>true</tt> if this map contains a mapping for the
* specified key.
*
* @param key The key whose presence in this map is to be tested
* @return <tt> true</tt> if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
/**
* Returns the entry associated with the specified key in the
* HashMap. Returns null if the HashMap contains no mapping
* for the key.
*/
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;
}
是否包含值
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested
* @return <tt> true</tt> if this map maps one or more keys to the
* specified value
*/
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
// 遍历整个table查询是否有相同的value值
for (int i = 0; i < tab. length ; i++)
// 遍历数组的每个链表
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value ))
return true;
return false;
}
/**
* Special -case code for containsValue with null argument
*/
private boolean containsNullValue() {
Entry[] tab = table;
for (int i = 0; i < tab. length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (e.value == null)
return true;
return false;
}
容量检查
/**
* Returns the number of key -value mappings in this map.
*
* @return the number of key- value mappings in this map
*/
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;
}