HashMap介绍
1.key-value键值对形式存储;
2.在HashMap中键不能重复,即只能有一个key存在,如果key值相同,value值会被覆盖。
3.key值和value值都可以为null。
4.数据不能保证一致性。
HashMap的属性
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认容量
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量是2的30次幂,超过这个容量用之替换
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子是哈希表在其容量自动增加之前可以达到多满的一种程度,默认加载因子0.75
transient Entry<k,v>[] table;//Entry数组保存键值对
transient int size; //键值对的数量
int threshold; // 扩容阈值:容量×加载因子得到
HashMap继承关系
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
底层数据结构
HashMap是一个线性数组实现的,里面有一个静态内部类Entry,
属性有key,value,next;所以数组就是Entry[],Map的内容都保存在Entry[]里面。 实则就是数组加链表组成。
构造函数
HashMap有4个构造器,其他构造器如果用户没有传入initialCapacity 和loadFactor这两个参数,会使用默认值
initialCapacity默认为16,loadFactory默认为0.75
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
// HashMap的最大容量只能是MAXIMUM_CAPACITY
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 设置 加载因子
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
public HashMap(Map<? extends K, ? extends V> m) {
// 设置容量大小 和 加载因子
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
inflateTable初始化table
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//找到小于或等于tosize的一个2的幂次方数。
int capacity = roundUpToPowerOf2(toSize);
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//将传入的数字i, 找到小于或等于i的一个2的幂次方数。
public static int highestOneBit(int i) { i=18 > 2^4 return 4;
// HD, Figure 3-1
i |= (i >> 1); -(1)
i |= (i >> 2); -(2)
i |= (i >> 4); -(3)
i |= (i >> 8); -(4)
i |= (i >> 16); -(5)
return i - (i >>> 1); -(6)
}
以二进制的形式进行运算
例:i=18
18 = 0001 0010
(1) 0001 0010右移一位: 0000 1001
0001 0010 或 0000 1001 : 0001 1011
(2) 0001 1011 右移2位: 0000 0110
0001 1011 或 0000 0110 :0001 1111
(3)0001 1111 右移4位: 0000 0001
0001 1111 或 0000 0000 : 0001 1111
(4)0001 1111 右移8位:0000 0000
0001 1111 或 0000 0000 :0001 1111
(5)0001 1111 右移8位:0000 0000
0001 1111 或 0000 0000 :0001 1111
(6)0001 1111 无符号右移1位:0000 1111
0001 1111 - 0000 1111 :0001 0000= 16=2^4
右移与或运算的目的就是想让某个数字的低位都变为1,再用该结果 减去 该结果右移一位后的结果,则相当于清零了原数字的低位。即得到了想要的结果。
put方法
1.如果table数组为null,首先进行数组初始化
2.利用index=(n-1)&hash的方式,找到索引位置
3.用key进行hash = hash(key)
4.如果索引位置无元素,则创建Node对象,存入数组该位置中
5.如果索引位置已有元素,说明hash冲突
6.若hash值和key值都一样,则进行value值的替代
7.hash值一致,key值不一致,且p为单链表结构,则往单链表中添加 ,追加到单链表末尾
public V put(K key, V value)
//如果table数组为null,首先进行数组初始化
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果key为null,调用putForNullKey方法将key放到数组0号下标位置
if (key == null)
return putForNullKey(value);
//利用index=(n-1)&hash的方式,找到索引位置
//用key进行hash= hash(key)
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//若hash值和key值都一样,则进行value值的替代
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; //并返回旧的value
}
}
modCount++;
//hash值一致,key值不一致,,则往单链表中添加 ,追加到单链表末尾
addEntry(hash, key, value, i);
return null;
}
------------------------------------------------------------------------------------
//和put相关的方法
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果size大于thresold(capacity*loadfactory) && 该位置下标不为空,进行数组的扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//2倍扩容
resize(2 * table.length);
//重新hash
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建节点
createEntry(hash, key, value, bucketIndex);
}
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++;
}
private V putForNullKey(V value) {
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;
}
//通过hash和table.length计算存储table下标
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
get方法
先对key进行hash,找到key存在的数组table的索引位置
对该位置链表进行遍历,找到判断key是否相等,(【hashcode】 【==】 【.equals()】 三种方法)
public V get(Object key) {
// 1. 当key == null时,则到 以哈希表数组中的第1个元素(即table[0])为头结点的链表去寻找对应 key == null的键
if (key == null)
return getForNullKey();
// 2. 当key ≠ null时,去获得对应值
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
//获取key值为null的value值
private V getForNullKey() {
//如果数据个数等于0,则返回null
if (size == 0) {
return null;
}
//获取key值为null的value
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
//计算key的hash值
int hash = (key == null) ? 0 : hash(key);
//通过hash值确定存储table的下表
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//1.先判断hash值是否相同,若相同则进行2
//2.通过==判断key值是否相同,相同则返回值,不同进行3
//3.通过.equals()方法判断是否相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
remove方法
remove流程
1.通过传入的key进行删除,返回key所对应的value;
2.如果当前table没有存储值,则返回null;
3.对key进行哈希过程,得到hash。然后indexFor通过hash和table.length得到该key所对应的索引位置。
4.对该位置的链表进行遍历,记录下遍历节点的前节点prev和当前节点e;
5.如果第一个节点是要删除的,将key对应在数组中的位置第一个置为next;若不是 则将前驱prev的next置为next。
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
//1.计算hash值
int hash = (key == null) ? 0 : hash(key);
// 2. 计算存储下标位置
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;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
// 若删除的是table数组中的元素(链表的头结点)
// 将头结点的next引用存入table[i]中
if (prev == e)
table[i] = next;
// 将以table[i]为头结点的链表中,当前Entry的前一个Entry中的next 设置为 当前Entry的next(直接跳过当前Entry)
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
containsKey方法
直接返回getEntry(key) != null,如果key对应的Entry不为空,则containsKey为true;
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
getEntry方法
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
// 1.通过hash(key)计算出对应的hash值
int hash = (key == null) ? 0 : hash(key);
// 2. 根据hash值计算出对应的数组下标
// 3. 遍历 以该数组下标的数组元素为头结点的链表所有节点,寻找该key对应的值
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
// 若 hash值 & key 相等,则证明该Entry是需要的键值对
// 通过equals()判断key是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
containsValue
如果value为null的话,对原table进行双重for循环遍历,找到value为null的就返回true;
如果value不为null,对原table进行双重for循环,找到value.equals(e.value);返回true;
public boolean containsValue(Object value) {
// 若value为空,则调用containsNullValue()
if (value == null)
return containsNullValue();
// 若value不为空,则遍历链表中的每个Entry,通过equals()比较values 判断是否存在
Entry[] tab = table;
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;//返回true
return false;
}
containsNullKey方法
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;
}