public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
//默认初始化容量(必须是2的幂)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//16
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//空内容的数组
static final Entry<?,?>[] EMPTY_TABLE = {};
//数据容器 - hash数组/hash表 - new Entry[16]{null,null,null,null,null,null,null...}
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
//元素个数
transient int size;//0
//阈值(容量*负载因子)
int threshold;//24
//负载因子
final float loadFactor;//0.75
//操作数
transient int modCount;//0
//hash种子数
transient int hashSeed = 0;
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//initialCapacity - 16
//loadFactor - 0.75f
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))//NaN - not a number
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
//交给子类去重写,实现子类的初始化过程
void init() {
}
//key - null
//value - "bbb"
public V put(K key, V value) {
//第一次添加时进入的判断
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
//获取key的hash值
//hash - 20
int hash = hash(key);
//通过hash值计算出在数组中下标
//i - 4
int i = indexFor(hash, table.length);
//hash冲突时才进入的循环
//e - 0x003
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;//Entry中的key
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//获取老的value
V oldValue = e.value;//玩游戏
//设置新的value
e.value = value;
e.recordAccess(this);
return oldValue;//返回老的value
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
//v - "bbb"
private V putForNullKey(V value) {
//e - 0x004
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
//获取老的value
V oldValue = e.value;
//设置新的value
e.value = value;
e.recordAccess(this);
return oldValue;//返回老的value
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
//hash - 0
//key - null
//value - "aaa"
//bucketIndex - 0
void addEntry(int hash, K key, V value, int bucketIndex) {
//判断是否扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容
resize(2 * table.length);
//重新计算出hash值
hash = (null != key) ? hash(key) : 0;
//重新计算出元素在数组中的下标
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//newCapacity - 32
void resize(int newCapacity) {
Entry[] oldTable = table;
//oldCapacity - 16
int oldCapacity = oldTable.length;
//如果长度等于最大容量
if (oldCapacity == MAXIMUM_CAPACITY) {
//将int最大值赋值给阈值,目的:减少扩容次数
threshold = Integer.MAX_VALUE;
return;
}
//创建新的hash表 - new Entry[32];
Entry[] newTable = new Entry[newCapacity];
//1.重新计算出hash种子数
//2.将table里的数据全部赋值给newTable
transfer(newTable, initHashSeedAsNeeded(newCapacity));
//将newTable的地址赋值给table
table = newTable;
//重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
//该方法在多线程下会出现hash回环的情况
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
//hash - 0
//key - null
//value - "aaa"
//bucketIndex - 0
void createEntry(int hash, K key, V value, int bucketIndex) {
//e - null
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
//h - 20
//length - 16
static int indexFor(int h, int length) {
//h - 0000,0000,0000,0000,0000,0000,0001,0100
//length-1 - 0000,0000,0000,0000,0000,0000,0000,1111
// 0000,0000,0000,0000,0000,0000,0000,0100
return h & (length-1);
}
//key - new Student("麻生希", '女', 26, "2308", "001")
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//toSize - 16
private void inflateTable(int toSize) {
// 获取参数的向上的2的幂(16返回16,17返回32)
//capacity - 16
int capacity = roundUpToPowerOf2(toSize);
//threshold - 12
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化hash表
table = new Entry[capacity];//new Entry[16];
//通过容量初识化hash种子数
initHashSeedAsNeeded(capacity);
}
//number - 16
private static int roundUpToPowerOf2(int number) {
if(number >= MAXIMUM_CAPACITY){
return MAXIMUM_CAPACITY;
}else{
if(number > 1){
return Integer.highestOneBit((number - 1) << 1);
}else{
return 1;
}
}
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//映射关系类/节点类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; ----------- 键
V value; --------------- 值
Entry<K,V> next; ------- 下一个节点的地址
int hash; -------------- key的hash值
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final V setValue(V newValue) {
V oldValue = value;//获取老的value
value = newValue;//设置新的value
return oldValue;//返回老的value
}
}
}
HashMap<Student, String> map = new HashMap<>();
map.put(new Student("麻生希", '女', 26, "2308", "001"),"写代码");
map.put(new Student("椎名空", '女', 23, "2308", "002"),"拍电影");
map.put(new Student("水菜丽", '女', 29, "2308", "003"),"玩游戏");
map.put(new Student("水菜丽", '女', 30, "2308", "003"),"看书");
map.put(null, "aaa");
map.put(null, "bbb");
- JDK1.7版本的HashMap的数据结构是什么?
一维数组+单向链表
- HashMap默认的初始化容量是多少?
16
- HashMap的容量为什么必须是2的幂?
如果不是2的幂,计算下标时length-1,得到的二进制位数上有可能是0,0位上计算出的结果一定是0,会让下标分布不均匀
- HashMap的最大容量是多少?
1<<30
- HashMap的最大容量为什么是1<<30?
HashMap容量为int类型
int类型的最大取值范围是2的31次方-1
1<<30,相当于2的30次方,该数字为int类型取值范围内最大的2的幂
- HashMap默认的负载因子是多少?其作用是什么?
0.75f
容量*负载因子=阈值(扩容临界点)
- HashMap默认的负载因子为什么是0.75?
取得了时间和空间的平衡
负载因子过大,比如为1,数组必须装满才扩容,利用了空间,浪费了时间
负载因子过小,比如为0.1,数组装一点数据就扩容,浪费了空间,利用了时间
- 如何理解HashMap的hash桶?
hash桶指的是hash表中某个下标上的单向链表
- 如何理解HashMap的hash碰撞/冲突?如何解决?
多个元素之间的hash值一样,在HashMap的数组中的下标也是一样的。
注意:hash碰撞会让hashmap处理数据的效率降低
解决方案:重写hashCode(),尽可能避免多个对象的hash值一样
HashMap存放null键的位置?
下标为0的位置
HashMap合适扩容?
元素个数大于等于阈值 并且 当前下标上的元素不为null
如何理解HashMap的hash回环?
多线程情况下,
一个线程在遍历数据
一个线程在添加数据,导致扩容,有可能形成闭环链表
JDK1.7版本和JDK1.8版本的HashMap的区别
JDK1.7版本的HashMap:一维数组+单向链表,头插法
JDK1.8版本的HashMap:一维数组+单向链表+平衡二叉树,尾插法
JDK1.8版本的HashMap为什么数据结构发生改变?
一维数组+单向链表 ,数组长度大于64并且链表长度大于8,会改成一维数组+平衡二叉树
因为数组长度大于64并且链表长度大于8时的数据量特别大,把单向链表变作为平衡二叉树,提高操作数据的效率
注意:二叉树的节点小于7,就会从二叉树变回为单向链表
JDK1.8版本的HashMap为什么链表长度大于8后变成二叉树
泊松分布