目录
1方法
方法 | 描述 |
---|---|
clear() | 删除 hashMap 中的所有键/值对 |
clone() | 复制一份 hashMap |
isEmpty() | 判断 hashMap 是否为空 |
size() | 计算 hashMap 中键/值对的数量 |
put() | 将键/值对添加到 hashMap 中 |
putAll() | 将所有键/值对添加到 hashMap 中 |
putIfAbsent() | 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。 |
remove() | 删除 hashMap 中指定键 key 的映射关系 |
containsKey() | 检查 hashMap 中是否存在指定的 key 对应的映射关系。 |
containsValue() | 检查 hashMap 中是否存在指定的 value 对应的映射关系。 |
replace() | 替换 hashMap 中是指定的 key 对应的 value。 |
replaceAll() | 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。 |
get() | 获取指定 key 对应对 value |
getOrDefault() | 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值 |
forEach() | 对 hashMap 中的每个映射执行指定的操作。 |
entrySet() | 返回 hashMap 中所有映射项的集合集合视图。 |
keySet() | 返回 hashMap 中所有 key 组成的集合视图。 |
values() | 返回 hashMap 中存在的所有 value 值。 |
merge() | 添加键值对到 hashMap 中 |
compute() | 对 hashMap 中指定 key 的值进行重新计算 |
computeIfAbsent() | 对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中 |
computeIfPresent() | 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。 |
2源码实现
2.1继承关系
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap继承自AbstractMap (该抽象类对Map接口的常用方法做了实现,方便子类复用
实现类Map接口,具有Map中提供的所有方法)
实现 Cloneable和Serializable接口
2.2构造函数
//通过初始容量和加载因子来实例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);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
//通过初始容量来实例HashMap,加载因子是默认值0.75
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//通过无参构造实例化 默认初始容量16 默认加载因子0.75
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//通过map集合实例构造HashMap实例
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
//初始化table、threshold参数,实例化哈希表
inflateTable(threshold);
//将map结合中的键值对存放到HashMap中
putAllForCreate(m);
}
2.3属性及默认值
//默认初始容量 :值为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子:0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认空表
static final Entry<?,?>[] EMPTY_TABLE = {};
//存放数据 table属性,是entry类型数组
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//存放的键值对个数
transient int size;
//扩容阈值 ,在扩容时判断 threshold =table.length()*loadFactor
int threshold;
//加载因子
final float loadFactor;
//版本控制
transient int modCount;
//key哈希相关
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
//key哈希相关
transient int hashSeed = 0;
2.4底层数据结构
//存放数据位置
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
}
通过以上可知:HashMap底层存放数据是数组加链表形式,即是哈希表结构
2.5扩容机制
扩容时机:在size>threshold时,会进行扩容
2.6常用方法
2.6.1插入元素:put(key,value)
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
//当table数组为空时,进入初始化table数据,当第一次调用put操作会进入
inflateTable(threshold);
}
//key为null,将数据存放在0号卡槽位置
if (key == null)
return putForNullKey(value);
//key不为null的处理
//通过key找到对应的存放卡槽位置
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//通过位置i找到卡槽i位置的数据链表,从头遍历,找key是否存在
//判断条件是hash和key,相等值更新
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;
}
}
//key在i位置不存在,需要新增entry节点存放数据
modCount++;
addEntry(hash, key, value, i);
return null;
}
private V putForNullKey(V value) {
//key为null将哈希位置为0号位置,需要遍历0号卡槽链表、判断key为null是否存在
//存在将entry中value进行跟新,返回旧的value中
//不存在则新建entry实体,存放put的数据
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哈希找到对应卡槽
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// 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);
}
通过key的哈希值找到在哈希表中的位置
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
容量是16
0001 0000 16
0000 1111 15
1010 1001
-------------
0000 1001 9
return h & (length-1);
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//当存放数据size大于扩容阈值threshold,进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//对HashMap进行2倍的扩容
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
//获取当前key应该插入的新卡槽位置
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//扩容
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//新创建数组为原来的2倍
Entry[] newTable = new Entry[newCapacity];
//将原map中的每一个entry实体进行重新哈希到新的map中
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
//threshold = table.length*loadFactor
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//采用头插法将数据插入
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
put操作步骤:
1、先判断key是否为null,特殊处理,存放在哈希表的0号卡槽位置
2、key为null的数据是否存在,遍历0号卡槽位置的链表,如果存在(==)则更新value,返回
3、如果不存在,新建节点(addentry)
4、key不为null,通过key来计算哈希值,找到在哈希表的卡槽位置(hash,indexFor)
5、在对应卡槽获取链表,遍历找key是否存在,如果存在(hash&&equals)则更新value,返回
6、key在链表不存在,新建节点(addEntry)
7、考虑是否扩容(size>threshold),需要扩容,将新的大小为原来的2倍,然后将原哈希表中的数据
都重新哈希到新的哈希表中, 并更新当前插入节点的卡槽位置
8、 采用头插入将新entry节点插入到给定的卡槽位置
2.6.2获取元素:get(key)
public V get(Object key) {
//key为null,直接到0号卡槽位置获取结果
if (key == null)
return getForNullKey();
//key不为null
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
//如果map为空,返回null
if (size == 0) {
return null;
}
//在0号卡槽位置,对链表遍历,查找key为null是否存在,存在则找entry中value返回,否则返回null
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
通过key找到对应entry实例
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//通过key的哈希找到key对应卡槽位置
int hash = (key == null) ? 0 : 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;
}
查询过程:
1、如果key为null,在0号卡槽位置遍历链表查询,key存在则返回entry的value,否则返回null
2、如果key不为null,对key进行哈希,找到对应的卡槽,遍历链表,判断key是否存在(hash,key.equals),返回entry的value否则返回null
2.6.3删除元素:remove(key)
通过key删除可以所在entry实体
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
//如果集合为空,直接返回null
if (size == 0) {
return null;
}
//通过key哈希找到对应卡槽(key为null卡槽为0)
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
//删除节点即解决单向链表的删除问题:解决思路:给定两个指针,两指针一前一后,前指针表示要删除的节点,
、 //通过后一个指针来将节点和前节点指针的next建立联系
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//如果删除的是头结点,将后一个节点作为当前卡槽的头结点
if (prev == e)
table[i] = next;
else
//后一个指针prex来将节点和前节点指针的next建立联系
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
删除过程:
1、通过key哈希来找到卡槽位置(key为null在0号卡槽)
2、对应卡槽的链表进行遍历查找,给定两个指针一前一后,前指针找到要删除节点,后指针建立和下一个节点关系
3特点总结
1、存储的数据是键值对形式,key不能重复,value值可以重复
2、key和value都可以为null
3、不能保证内部元素的顺序
4、底层数据结构是哈希表