Hash表是基于Map接口实现。HashMap实现了Map接口的所有方法。
HashMap用允许有null的值value,也允许有null的键key(只要使用Node这样类似的封装item就允许为空,但是也不完全,ArrayList就是例外)。HashMap与HashTable基本一致,只不过HashMap不是线程安全的,并且允许有null的键和值。而HashTable是线程安全的,并且既不允许键为空,也不允许值为空。
HashMap中有两个参数对于性能是很重要的,一个是初始容量initial capacity,另一个是负载因子load factor。capacity是hash表中的桶的数量。负载因子是hash表在内部的桶的容量自动扩容之前可以达到多满的一种衡量指标。当hash表中存放的键值对超过了capacity*load factor的时候,hash表将会扩容并rehash。默认的负载因子是0.75。
如果需要向hashmap中添加非常多的键值对,那么最好在创建HashMap时指定一个较为充足的初始容量,这样可以减少很多的rehash操作,而rehash操作是比较费时的。
需要注意的是HashMap并不是线程安全的,如果某个或者多个线程对hashMap进行了结构化修改,那么多个线程不能并发的访问hashmap。如果需要多线程下安全运行,可以在外部使用加锁,或者使用synchronizedMap或者使用ConcurrentHashMap。
声明
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
//代码
}
HashMap继承了AbstractMap并实现了Map接口。
成员变量
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
//用于支持序列化机制。
private static final long serialVersionUID = 362498820763181265L;
/**
* hashmap的初始容量,必须是2的n次方。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量,如果带参数的构造方法指定了更大的值,那么使用这个限定。
* 注意这个是可以指定的,稍后在resize()中可以看到内部的桶数组的最大容量是Integer.MAX_VALUE。
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的负载因子。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 转化为树的阈值,如果同一个桶中的元素(发生了hash冲突)超过了这个阈值,那么链表将
* 转化为树。当进行删除等操作时,树又可以转化为链表。
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 当同一个桶中的元素树化了之后。当进行删除等操作时,桶中的元素达到该值时,树又可以转化为链表。
* 注意之所以是6,而不是7,是为了防止如果一个桶中的元素数量在7与8之间徘徊时(即频繁的插入删除)
* 那么可以防止频繁的树化和链表化的操作,这个还是挺耗性能的。
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 只有当hash表的桶数组的容量达到了该值才有可能会进行树化操作,否则优先扩容hash表的桶数组,并且resize。
* 该值至少应该是4*TREEIFY_THRESHOLD,以避免resize操作和树化操作冲突。
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 桶数组,当在第一次使用的时候初始化,并且随着元素的增加可以扩容。其容量总是2的n次方。
*/
transient Node<K,V>[] table;
/**
* 用于entrySet方法返回的结果。
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* hashmap中包含的键值对的数量。
*/
transient int size;
/**
* 表示该hashmap被结构化修改的次数,注意rehash也是结构化修改。该字段通常用于迭代器遍历。
*/
transient int modCount;
/**
* 内部桶数组扩容的条件, threshold=capacity*load factor
* 如果数组还没有被分配的时候,该字段的值是数组的初始容量,或者是0以表示DEFAULT_INITIAL_CAPACITY
* @serial
*/
int threshold;
/**
* 加载因子
*/
final float loadFactor;
}
Node——键值对的外衣
hashmap内部会将我们放入的键值对封装成Node节点放入内部的桶中,该结点实现了Map.Entry接口。
/**
* 静态内部类Node节点。
*/
static class Node<K,V> implements Map.Entry<K,V>
{
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next)
{
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey()
{
return key;
}
public final V getValue()
{
return value;
}
public final String toString()
{
return key + "=" + value;
}
//node的hashcode是用其key与value的hashcode相异或得到的。
public final int hashCode()
{
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue)
{
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o)
{
if (o == this)
return true;
if (o instanceof Map.Entry)
{
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
构造方法
/**
* 根据传入的初始容量和负载因子创建hashmap。
* 注意并不是直接使用我们传入的负载因子,而是寻找第一个大于等于该值的第一个2的n次方。
* 这里并没有为桶数组分配空间,而是使用threshold保存下来。
* threshold标示底层桶数组下次扩容的时机。
* 通常情况下threshold=capacity*loadFactor。这里是例外
*/
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);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
/**
* 返回大于等于cap的第一个2的幂次方。
* 注意如果cap本身就是一个2的次方则返回他本身。
*/
static final int tableSizeFor(int cap)
{
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/**
* 根据传入的初始值与默认的负载因子构建一个空的hashmap。
*/
public HashMap(int initialCapacity)
{
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 构建一个空的hashmap,使用默认的初始容量(16),默认的负载因子(0.75)
*/
public HashMap()
{
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* 构建一个新的hashmap,并将参数map中的所有键值对复制到新创建的map中,负载因子为
* 默认的0.75。初始容量足以容纳下m中的所有元素。
*/
public HashMap(Map<? extends K, ? extends V> m)
{
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
/**
* 1、首先检查m中是否有键值对。如果没有不做操作
* 2、如果是新创建的hash表,新数组的容量为第一个大于(m中k-v对的数量/loadFctor +1)的二的n次幂
* 更新threshold。
* 3、如果桶数组不为空,那么执行resize()方法,迁移到新数组中。
* 4、通过putVal方法将m中的键值对放入新hash表中。
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict)
{
int s = m.size();
if (s > 0)
{
if (table == null)
{ // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
{
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
/**
* 计算key.hashCode()并将哈希的较高位(XOR)扩展至低位。 由于该表使用2的幂次方掩码,因此
* 仅在当前掩码前方的位中发生变化的哈希码的集合将始终发生冲突。 (众所周知的示例是在小表中包含
* 连续整数的Float键集。)因此,应用了一种变换,将向后扩展较高位的影响。 在速度,实用性和位扩展质量之
* 间需要权衡。 由于许多常见的哈希集已经合理分布(因此无法从扩展中受益),并且由于我们使用树来处理容器
* 中的大量冲突,因此我们仅以影响最小的方式对一些移位后的位进行XOR,以减少系统损失, 以及合并最高位的
* 影响,否则由于表范围的限制,这些位将永远不会在索引计算中使用。
*/
static final int hash(Object key)
{
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* 如果桶数组为空,那么初始化数组,否则将数组的容量翻倍。
* 初始化时将根据之前构造方法中保存在threshold的值初始化数组。
* 其他情况将新创建一个原来数组容量两倍的数组,然后将之前数组的键值对转移到新的数组中。
*/
final Node<K,V>[] resize()
{
Node<K,V>[] oldTab = table;
//判断之前的数组是否已经初始化。
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//1.数组已经初始化过了,那么将是扩容操作
if (oldCap > 0)
{
if (oldCap >= MAXIMUM_CAPACITY)
{
//指定阈值为Integer.MAX_VALUE
threshold = Integer.MAX_VALUE;
return oldTab;
}
//将新数组的容量扩容为原来的两倍。同时将阈值也相应的扩容为原来的两倍。
//这与newCap*loadFactor是一样的。
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//2.是初始化操作,并且在创建时在构造方法中指定了初始容量,计算后的桶数组的初始值
//被保存到了threshold中。
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//3.是初始化操作,但是在创建时并没有指定初始容量,将使用默认的初始容量。
else
{ // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//这个对应情况2
if (newThr == 0)
{
//计算新的threshold
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//使用计算的新容量创建桶数组。创建数组的动作。
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
//如果是扩容,那么需要将旧数组的键值对复制到新数组中,初始化则不必。
if (oldTab != null)
{
for (int j = 0; j < oldCap; ++j)
{
Node<K,V> e;
//旧数组中位置j处存放着键值对。
if ((e = oldTab[j]) != null)
{
//置空,便于gc
oldTab[j] = null;
//位置j处只存放了一个键值对。也就是没有发生hash冲突。
//使用元素的hash值与新数组的容量相与得到下标。
//新数组只有在容量为2的次方时才可以这么使用。
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//如果同一个桶中冲突的node节点过多,已经进行了树化操作。
else if (e instanceof TreeNode)
//此处将树中的所有节点移动到新的桶数组中。
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//位置j处存在hash冲突,但是冲突元素以链表的形式连接在一起。
else
{ // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do
{
next = e.next;
//这里将旧数组的元素分为了两类。
//由于oldCap是2的n次方。那么它的二进制位就是首位为1,其余位为0。
//而要想两者相与为0,那么元素对应oldCap首位那个位置应该为0才可以。
//假如oldCap=8,其二进制表示为1000,那么oldCap-1=>0111。
//那么如果e.hash倒数第四位为0的话,相当于之后后三位参与运算。
//这样当扩容后,新的newCap=2*oldCap=16。其二进制=>10000.
//newCap-1=>01111。相当于后四位参与运算,但是我们说e.hash的倒数第四位是0啊。
//这样就跟后三位参与运算完全没有区别了。
//所以结论就是如果(e.hash & oldCap) == 0。那么元素在新数组的位置将于老数组的位置相同
//否则元素在新数组的位置=老数组的位置+老数组的容量。
//lo代表数组低位。
//hi代表数组的高位。
if ((e.hash & oldCap) == 0)
{
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else
{
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
}
while ((e = next) != null);
//将冲突链表转移到新数组中相同的位置。
if (loTail != null)
{
loTail.next = null;
newTab[j] = loHead;
}
//将冲突链表转移到新数组中向右偏移oldCap的位置。
if (hiTail != null)
{
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}//end else
}//end if
}//end for
}//end if
return newTab;
}
/**
* 将键值对放入桶数组的适当位置中。
* onlyIfAbsent表名只有再之前没有添加对应的键值对时才会添加进去,否则不做操作
* 默认是进行覆盖操作。
* 1、如何还没有初始化,那么先执行初始化
* 2、如果桶数组位置还没有放入任何元素,那么直接创建节点插入该位置。
* 3、发生冲突了,通过equals判断与第一个元素是不是相等,相等则根据选择替换覆盖或者不做操作。
* 4、第一个元素不相等,则判断是红黑树还是链表,红黑树就在红黑树中查找插入,链表就在链表中查找插入。
* 5、如果是链表,则依次判断链表中的元素,如果到链表尾部仍然没有相等的,那么在链表的末尾新创建一个节点插入
* 并判断是否元素个数达到了8个,如何是则转化为红黑树。
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict)
{
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果还没有初始化,那么执行初始化。
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果该位置目前还没有存放任何的元素。那么直接创建一个新的Node节点
//将键值对插入该位置。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//说明发生了hash冲突。
else
{
Node<K,V> e; K k;
//如果该位置的元素的hash值与要插入的hash值相等,并且键也相等。
//那么是妥妥的hash冲突了。这时已经找到原先的key-value的node节点了,不再需要遍历。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果该桶位置的冲突实在太多,已经树化了。那么就调用相应方法放到红黑树中。
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//确实发生了冲突,但是同一个桶内的元素是按照链表的形式存放的。
else
{
for (int binCount = 0; ; ++binCount)
{
//这里使用的是尾插法,将其插入到冲突链表的尾部。
if ((e = p.next) == null)
{
p.next = newNode(hash, key, value, null);
//注意binCount从0开始当其>=7的时候,也就是已经第8个元素了,
//也就是树化的条件就是一旦同一个桶中的元素数量达到了8个那就就将
//链表转化为红黑树,而不是超过了8个。
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//hash值肯定相同了,因为都定位到同一个桶了,如果桶中有元素的key与该key相同
//那么需要决定是否要覆盖原先的值。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//到这里说明hash表中已经存在相应的key了。
if (e != null)
{ // existing mapping for key
V oldValue = e.value;
//可以进行替换。或者即使我们选择不能替换,但是之前插入的时候存放的值为null,这里仍然可以替换。
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//这一步是回调方法,回调子类重写的,方便linkedHashMap的后操作。
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//如果添加元素导致了size超过了阈值。那么需要扩容,并resize操作。
if (++size > threshold)
resize();
//这一步是回调方法,回调子类重写的,方便linkedHashMap的后操作。
afterNodeInsertion(evict);
return null;
}
HashMap一共提供了四种构造方法,主要用来指定初始容量和负载因子,以及利用其它的map来构建hash表。而初始容量和负载因子对于hash表的性能有很大的影响。从构造函数可以看到,HashMap是延迟初始化的,只有当真正的第一次往桶数组中放入键值对的时候才会触发初始化操作。
常用方法
获取信息
size方法
/**
* 返回hash表中的键值对的数量。
*/
public int size()
{
return size;
}
isEmpty方法
/**
* 如果hash表中不存在任何的键值对,那么将返回true,否则返回false。
*/
public boolean isEmpty()
{
return size == 0;
}
keySet方法
/**
* 返回该map中的所有的key对应的Set视图,如果改变map中的key,将会影响到Set集合,反之亦然。
* 如果在返回的set上使用迭代器遍历元素的过程中,map被修改了,那么将会发生不可预知的结果。
* 返回的Set实例可以删除key,这样对应的map映射就也会相应的被删除。
* 但是不能往这个Set中添加元素。
*/
public Set<K> keySet()
{
//这个属性是继承自AbstractMap
Set<K> ks = keySet;
if (ks == null)
{
ks = new KeySet();
keySet = ks;
}
return ks;
}
/**
* 内部类的形式,继承自AbstractMap。
* 可以看到完全是委托内部的map的一系列操作。所以他只是一个视图
*/
final class KeySet extends AbstractSet<K>
{
public final int size()
{
return size;
}
public final void clear()
{
HashMap.this.clear();
}
public final Iterator<K> iterator()
{
return new KeyIterator();
}
public final boolean contains(Object o)
{
return containsKey(o);
}
public final boolean remove(Object key)
{
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator()
{
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action)
{
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null)
{
int mc = modCount;
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
通过如下代码可以测试一下Set视图与map之间的相互影响性。
HashMap<String, String> map = new HashMap<>();
map.put("1","1");
map.put("2","2");
map.put("3","3");
Set<String> set = map.keySet();
System.out.println(map.containsKey("1"));
set.remove("1");
System.out.println(map.containsKey("1"));
如果尝试往这个Set集合视图中添加元素,那么你将收获一个异常大礼包
HashMap<String, String> map = new HashMap<>();
map.put("1","1");
map.put("2","2");
map.put("3","3");
Set<String> set = map.keySet();
set.add("4");
values方法
/**
* 返回map中包含的所有键值对的所有value的Collection视图,如果改变map中的value,将会影响到Collection集合,反之亦然。
* 如果在返回的Collection上使用迭代器遍历元素的过程中,map被修改了,那么将会发生不可预知的结果。
* 返回的Collection实例可以删除value,这样对应的map映射就也会相应的被删除。
* 但是不能往这个Collection中添加元素。
*/
public Collection<V> values()
{
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
但是我们会想可能存在多个键映射到一个value的情况,那么它是删除所有的键值对映射呢还是只删除其中一个呢。
HashMap<String, String> map = new HashMap<>();
map.put("狗剩","单身");
map.put("狗娃","单身");
map.put("狗蛋","不单身");
Collection<String> values = map.values();
values.remove("单身");
for (Map.Entry<String, String> entry : map.entrySet())
{
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
可以看到他只是删除了一个键值对映射,具体是从桶数组的前面开始遍历,找到了桶中存在指定的value后就将该键值对映射删除掉然后返回。
final class Values extends AbstractCollection<V>
{
public final int size()
{
return size;
}
public final void clear()
{
HashMap.this.clear();
}
public final Iterator<V> iterator()
{
return new ValueIterator();
}
public final boolean contains(Object o)
{
return containsValue(o);
}
public final Spliterator<V> spliterator()
{
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action)
{
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null)
{
int mc = modCount;
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
entrySet方法
/**
* 返回一个该map中包含的所有键值对映射的Set集合视图,如果改变map,那么Set集合会相应的收到影响,反之亦然。
* 如果在返回的Set上使用迭代器遍历元素的过程中,map被修改了,那么将会发生不可预知的结果。
* 返回的Set实例可以删除指定元素,这样对应的map映射就也会相应的被删除。
* 但是不能往这个Set中添加元素。
*/
public Set<Map.Entry<K,V>> entrySet()
{
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>>
{
public final int size()
{
return size;
}
public final void clear()
{
HashMap.this.clear();
}
public final Iterator<Map.Entry<K,V>> iterator()
{
return new EntryIterator();
}
public final boolean contains(Object o)
{
//根据其key和key的hash值在map中查找。
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);//从这里可以看出Entry实际上就是Node。
}
public final boolean remove(Object o)
{
if (o instanceof Map.Entry)
{
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator()
{
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action)
{
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null)
{
int mc = modCount;
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
获取元素
get方法
/**
* 返回key所对应的value。如果hash表中不存在指定的key那么将返回null。
* 注意返回null,并不一定意味着该hashmap中没有指定key的映射。也有可能某个指定的key对应的value就是null。
* 通过containsKey方法可以区别。
*/
public V get(Object key)
{
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* 根据key和其hash值找到元素对应的Node节点并返回。
* 1、首先看桶数组是否为空,为空表示还未初始化或者已经被删除了,直接返回null.
* 2、根据key的hash值定位到对应的桶数组,如果该位置为空,表示该桶中不存在元素,直接返回null.
* 3、然后检查第一个节点equals方法是否返回true,如果是,直接返回第一个节点。
* 4、如果第一个节点不相等,那么检查桶是否已经树化,如果是,就在红黑树中查找。否则在链表中查找。
* 5、如果都没有找到,返回null。
*/
final Node<K,V> getNode(int hash, Object key)
{
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果桶数组为空,表示还没有初始化,直接返回null。
//或者已经初始化了,但是里面的元素都没删干净了。
//如果通过hash值定位到的桶的位置那个地方为null。表示该桶中不存在任何元素,那么直接返回null。
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null)
{
//先检查第一个节点,找到的条件是hash值相等并且key也相等。
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null)
{
//桶中的元素已经树化,在红黑树中查找。
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do
{
//找到的条件是hash值相等并且key也相等。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
getOrDefault方法
/**
* 如果map中包含key的映射,那么返回其value,否则返回其传入的默认值。
*/
@Override
public V getOrDefault(Object key, V defaultValue)
{
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
containsKey方法
/**
* 如果根据给定的key和其相应的hash值可以查到相应的结点。那么将返回true。
* 如果查找不到相应的结点,则返回null。
*/
public boolean containsKey(Object key)
{
return getNode(hash(key), key) != null;
}
containsValue方法
/**
* 如果该map中有一个或者多个key映射的value是我们传入的value。那么就返回true,否则返回false。
*/
public boolean containsValue(Object value)
{
Node<K,V>[] tab; V v;
//如果桶数组还没初始化或者已经被删干净了那还遍历个毛毛了。
if ((tab = table) != null && size > 0)
{
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
{
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
添加元素
put方法
/**
* 将一个键值对映射添加到hash表中,如果之前已经存在该key的键值对映射,那么将使用value替换
* 掉旧值。
* 第三个参数onlyIfAbsent为false表示当hash表中存在key时替换掉该value。为true时,当已经有key的时候不进行任何的操作。
* 该方法使用putVal实现。
*/
public V put(K key, V value)
{
return putVal(hash(key), key, value, false, true);
}
putIfAbsent方法
/**
* 第三个参数onlyIfAbsent为true,表名只有在map中不存在key所指定的键值对时才会将键值对放入map。
* 如果已经存在将不做任何的修改。
*/
@Override
public V putIfAbsent(K key, V value)
{
return putVal(hash(key), key, value, true, true);
}
putAll方法
/**
* 将m中的键值对复制到当前的hashmap中,如果m中包含当前hashmap已经有的键,那么将替换掉key对应的value。
* 该方法使用putMapEntries实现,跟传map参数的构造方法差不多。
* 在hashmap中,第二个参数evict毫无用处,该方法用于linkedHashMap回调用的。
*/
public void putAll(Map<? extends K, ? extends V> m)
{
putMapEntries(m, true);
}
删除元素
remove方法
/**
* 如果该map中存在key到value的映射,那么根据key和其hash值使用removeNode删除指定的结点。并返回该节点中存储的value。
* 如果不存在,返回null。
*/
public V remove(Object key)
{
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* 如果该map中存在key到value的映射,那么根据key和其hash值以及value使用removeNode删除指定的结点。
* 并返回该节点中存储的value。注意这个时候只有当key的hash值,key,value都相等时才会删除掉。
* 如果不存在,返回null。
*/
@Override
public boolean remove(Object key, Object value)
{
return removeNode(hash(key), key, value, true, true) != null;
}
/**
* 根据hash值与key,value等信息删除指定的结点并返回该节点。
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable)
{
Node<K,V>[] tab; Node<K,V> p; int n, index;
//如果桶数组还没有初始化,那么直接返回null.
//如果桶数组中的键值对都已经被删干净了,那么也直接返回null。
//如果根据hash值定位到桶数组中指定位置为null,那么也不存在要删除的结点,直接返回null。
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null)
{
Node<K,V> node = null, e; K k; V v;
//检查该位置的第一个节点。如果是要删除的那就不用继续往后找了。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null)
{
//第一个节点不是,该桶中冲突的元素太多,已经树化了。那么就从树中寻找。
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else
{
//冲突的结点还是以链表的形式存储。那就赶着往后遍历吧。
do
{
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k))))
{
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
//注意找的时候先不管value,在这里再看找到的结点是否需要匹配value。
//如果node==null,代表没找到。
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v))))
{
//如果要删除的结点在红黑树中,那么使用树的删除方法。
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//如果要删除的结点就是第一个节点,那么直接用它的后继节点代替就可以了。
else if (node == p)
tab[index] = node.next;
//要删除的结点是链表中的某一个节点。p是node节点的前驱结点。
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}//end if
return null;
}
//回调方法,供子类实现。
void afterNodeRemoval(Node<K,V> p) { }
clear方法
/**
* 删除该map中的所有的键值对映射,直接将桶数组的每个桶置空,就是这么简单粗暴。
* 剩下的交给垃圾收集器就可以了。
*/
public void clear()
{
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0)
{
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;
}
}
替换元素
replace方法
/**
* 当key的hash值,key,value都相等时找到的Node节点的value使用newValue替换
*/
@Override
public boolean replace(K key, V oldValue, V newValue)
{
Node<K,V> e; V v;
if ((e = getNode(hash(key), key)) != null &&
((v = e.value) == oldValue || (v != null && v.equals(oldValue))))
{
e.value = newValue;
afterNodeAccess(e);
return true;
}
return false;
}
/**
* 当key的hash值,key相等时找到的Node节点的value使用value替换
* 这时不会比较oldValue,并会返回oldValue。
*/
@Override
public V replace(K key, V value)
{
Node<K,V> e;
if ((e = getNode(hash(key), key)) != null)
{
V oldValue = e.value;
e.value = value;
afterNodeAccess(e);
return oldValue;
}
return null;
}
/**
* 对map中的所有的键值对应用接口中的方法进行修改,该方法执行完成后,整个map中的键值对将进行一次
* 大换血。
*/
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
{
Node<K,V>[] tab;
if (function == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null)
{
int mc = modCount;
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
{
e.value = function.apply(e.key, e.value);
}
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
java8新增API
这里分析几个方法,其他方法逻辑差不多,只是相应的接口实现的功能不同
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction)
{
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
//先检查是否有必要进行扩容。
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
//1如果桶中不存在元素,那么肯定不存在了,直接执行相应的逻辑就可以了。
if ((first = tab[i = (n - 1) & hash]) != null)
{
//2首节点是树节点
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else
{
//3在链表中。
Node<K,V> e = first; K k;
do
{
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
{
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
V oldValue;
//说明找到了键值对映射的Node节点。
//通过该方法名就可以只要在找不到时才会执行相应的逻辑,现在是找到了,那么直接返回。
//注意如果之前该节点的key对应的value就是设置的null,那么也会执行相应的逻辑。
if (old != null && (oldValue = old.value) != null)
{
afterNodeAccess(old);
return oldValue;
}
}
//执行接口中的方法。
V v = mappingFunction.apply(key);
if (v == null)
{
return null;
}
//对应情况3.重新调整节点的值为接口方法计算过后的值。并且找到了相应的结点。
else if (old != null)
{
old.value = v;
afterNodeAccess(old);
return v;
}
//对应情况2节点在树中
else if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else
{
//对应情况1或者压根没有找到对应的key。
//说明桶中根本不存在元素,那么直接创建一个新的节点插入。
//这就是为什么这里需要检查扩容
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
return v;
}
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction)
{
if (remappingFunction == null)
throw new NullPointerException();
Node<K,V> e; V oldValue;
int hash = hash(key);
//该方法必须存在时才能进行相应的操作,因此可以通过getNode方法先找到该节点。
//如果找不到,那么啥也不搞。
if ((e = getNode(hash, key)) != null &&
(oldValue = e.value) != null)
{
V v = remappingFunction.apply(key, oldValue);
//如果计算结果不为空,那么修改value值。注意如果计算结果为空,那么将删除指定key对应的value是Null的
//键值对。
if (v != null)
{
e.value = v;
afterNodeAccess(e);
return v;
}
else
removeNode(hash, key, null, false, true);
}
return null;
}
/**
* 从名字可以看出该方法村部存在都执行相应的逻辑。
*/
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction)
{
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
//先检查是否有必要进行扩容。
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
//1如果桶中不存在元素,那么肯定不存在了,直接执行后续相应的逻辑就可以了。
if ((first = tab[i = (n - 1) & hash]) != null)
{
//2首节点是树节点
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else
{
//3在链表中。
Node<K,V> e = first; K k;
//遍历链表,要么找到该节点,要么桶中不存在指定key对应的结点。
do
{
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
{
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
V oldValue = (old == null) ? null : old.value;
//执行相应的接口逻辑。
V v = remappingFunction.apply(key, oldValue);
//找到了相应的Node结点,也就是map中存在指定key的键值对映射。
if (old != null)
{
//如果计算结果不为空,那么替换掉旧值。
if (v != null)
{
old.value = v;
afterNodeAccess(old);
}
//计算结果为空,那么注意如果计算结果为空,那么将删除指定key对应的value是Null的
//键值对。
else
removeNode(hash, key, null, false, true);
}
//没有找到相应的结点。也就是map中不存在相应的键值对映射。那么需要看接口实现类计算的
//结果是否为null,不为空就创建新节点插入进去,为空就不搞了。
else if (v != null)
{
//查到树中。
if (t != null)
t.putTreeVal(this, tab, hash, key, v);
else
{
//该方法采用的是头插法。
tab[i] = newNode(hash, key, v, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return v;
}
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction)
{
if (value == null)
throw new NullPointerException();
if (remappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab; Node<K,V> first; int n, i;
int binCount = 0;
TreeNode<K,V> t = null;
Node<K,V> old = null;
if (size > threshold || (tab = table) == null ||
(n = tab.length) == 0)
n = (tab = resize()).length;
if ((first = tab[i = (n - 1) & hash]) != null)
{
if (first instanceof TreeNode)
old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
else
{
Node<K,V> e = first; K k;
do
{
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
{
old = e;
break;
}
++binCount;
} while ((e = e.next) != null);
}
}
if (old != null)
{
V v;
if (old.value != null)
v = remappingFunction.apply(old.value, value);
else
v = value;
if (v != null)
{
old.value = v;
afterNodeAccess(old);
}
else
removeNode(hash, key, null, false, true);
return v;
}
if (value != null)
{
if (t != null)
t.putTreeVal(this, tab, hash, key, value);
else
{
tab[i] = newNode(hash, key, value, first);
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
}
++modCount;
++size;
afterNodeInsertion(true);
}
return value;
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action)
{
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null)
{
int mc = modCount;
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
Hashmap内部的序列化规则
writeObject方法
/**
* 保存hashmap状态到流中。
*/
private void writeObject(java.io.ObjectOutputStream s)
throws IOException
{
int buckets = capacity();
// Write out the threshold, loadfactor, and any hidden stuff
s.defaultWriteObject();
s.writeInt(buckets);
s.writeInt(size);
internalWriteEntries(s);
}
// Called only from writeObject, to ensure compatible ordering.
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException
{
Node<K,V>[] tab;
if (size > 0 && (tab = table) != null)
{
for (int i = 0; i < tab.length; ++i)
{
for (Node<K,V> e = tab[i]; e != null; e = e.next)
{
s.writeObject(e.key);
s.writeObject(e.value);
}
}
}
}
/**
* 从流中重建hashmap
*/
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0)
{ // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++)
{
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}
HashMap内部的迭代器实现
HashIterator
abstract class HashIterator
{
Node<K,V> next; // 下一个将要返回的entry。
Node<K,V> current; // 当前正在访问的entry
int expectedModCount; // fast-fail机制
int index; // 当前下标
HashIterator()
{
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0)
{ // 提前找到第一个entry
do
{
}while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext()
{
return next != null;
}
final Node<K,V> nextNode()
{
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null)
{
//找到下一个不为空的桶。
do
{
}
while (index < t.length && (next = t[index++]) == null);
}
return e;
}
//内部还是使用removeNode方法实现的。
public final void remove()
{
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
KeyIterator
final class KeyIterator extends HashIterator
implements Iterator<K>
{
public final K next()
{
return nextNode().key;
}
}
ValueIterator
final class ValueIterator extends HashIterator
implements Iterator<V>
{
public final V next()
{
return nextNode().value;
}
}
EntryIterator
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>>
{
public final Map.Entry<K,V> next()
{
return nextNode();//可以看到Node就是内部的HashMap的entry实现。
}
}
其他方法
/**
* 如果x直接实现了Comparable接口,那么返回其class,否则返回null。
*/
static Class<?> comparableClassFor(Object x)
{
if (x instanceof Comparable)
{
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // 跳过检查
return c;
//获取x直接实现的接口
if ((ts = c.getGenericInterfaces()) != null)
{
for (int i = 0; i < ts.length; ++i)
{
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
/**
* 如果x是kc类型的(实现Comparable接口)并且不为空,返回k.compareTo(x)
*/
@SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable
static int compareComparables(Class<?> kc, Object k, Object x)
{
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
/**
* 将冲突的链表转化为红黑树。
*/
final void treeifyBin(Node<K,V>[] tab, int hash)
{
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null)
{
TreeNode<K,V> hd = null, tl = null;
do
{
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else
{
p.prev = tl;
tl.next = p;
}
tl = p;
}
while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
// 将Node节点转化为TreeNode节点。
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next)
{
return new TreeNode<>(p.hash, p.key, p.value, next);
}
/**
* 返回该hashmap实例的一个浅复制,其中的key和value都没有复制。
*/
@Override
public Object clone()
{
HashMap<K,V> result;
try
{
result = (HashMap<K,V>)super.clone();
}
catch (CloneNotSupportedException e)
{
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
result.reinitialize();
result.putMapEntries(this, false);
return result;
}
final float loadFactor()
{
return loadFactor;
}
final int capacity()
{
//有可能还没有初始化,但是构造方法中指定了容量。
return (table != null) ? table.length :
(threshold > 0) ? threshold :
DEFAULT_INITIAL_CAPACITY;
}
关于红黑树部分
关于Hashmap中红黑树的相关操作的代码这里不再分析。