public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
//默认初始化容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //16
//hash数组最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//空内容的数组
static final Entry<?,?>[] EMPTY_TABLE = {};
//hash数组/表
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//new Entry[16];
//元素个数
transient int size;//3
//阈值
int threshold;//12
//负载因子
final float loadFactor;//0.75f
//操作数
transient int modCount;//3
//种子数
transient int hashSeed = 0;
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//initialCapacity - 16
//loadFactor - 0.75
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();
}
// 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值(hashCode() + 散列算法)
int hash = hash(key);
//通过hash值计算出在数组中的下标 i-2
int i = indexFor(hash, table.length);
//e -- 李林的Entry对象
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//判断是否是同一个key的标准
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//oldValue - 吃火锅
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
//返回被替换的值
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
// value - "bbb"
private V putForNullKey(V value) {
//e - 0x004
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
//oldValue - aaa
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
//返回被替换的值
return oldValue;
}
}
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);
//重新计算key的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;
//如果原数组的长度等于数组的最大值(1<<30)
if (oldCapacity == MAXIMUM_CAPACITY) {
//将int类型的最大值赋值给阈值
threshold = Integer.MAX_VALUE;
//直接结束该方法
return;
}
Entry[] newTable = new Entry[newCapacity];
//transfer的作用:
//1.将原数组的数据全部迁移到新数组中
//2.重新计算hash种子数
transfer(newTable, initHashSeedAsNeeded(newCapacity));
//将新数组的地址赋值给原数组的引用
table = newTable;
//重新计算阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
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 - 2345672
//length - 16
static int indexFor(int h, int length) {
return h & (length-1);
}
// k - new Student("水菜", '女', 25, "2204", "001")
final int hash(Object k) {
//获取hash种子数
int h = hashSeed;
//判断k如果是String类型,字符串的hash值+散列算法+hashSeed计算出对象hash值
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) {
//roundUpToPowerOf2()传入int值,返回大于或等于参数的2的幂的数字
//capacity - 16
int capacity = roundUpToPowerOf2(toSize);
//threshold - 12
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化hash数组
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {
//Integer.highestOneBit(数字)只保留该数字二进制最高位的1,其余的1变作为0
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//根据长度初始化hash种子数
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
//HashMap的映射关系类/节点类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; --- key
V value; --- 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;
}
}
}
HashMap<Student, String> map = new HashMap<>();
map.put(new Student("水菜", '女', 25, "2204", "001"), "拍黄瓜");
map.put(new Student("天使", '女', 21, "2204", "002"), "吹唢呐");
map.put(new Student("李林", '男', 22, "2204", "003"), "吃火锅");
map.put(new Student("李林", '男', 22, "2204", "003"), "写代码");
map.put(null, "aaa");
map.put(null, "bbb");
- JDK1.7版本的HashMap底层数据结果是什么?
一维数组 + 单向链表
- 什么是Hash桶?
单向链表
- 什么是hash碰撞?
多个key的hash值相同,在数组中的下标也是相同的,就会判断两个对象是否相同,这个过程叫做hash碰撞。
hash碰撞应该要避免,因为一维数组查询快,而单向链表查询慢,hash碰撞后如果有单向链表的存在,势必会影响HashMap的查询效率
- HashMap底层一维数组的初始化长度为多少?
1<<4 --> 16
- HashMap底层一维数组的长度为什么必须是2的幂?
计算元素在数组中的下标的代码是:h & (length-1)
长度不是2的幂,长度-1结果的二进制表示位数上有可能出现0,&的结果位数上就一定是0,导致元素在数组中的分布不均匀
- HashMap的一维数组的最大长度是什么?
1 << 30; —> 1073741824
- HashMap的一维数组的最大长度为什么是1<<30?
最大长度的类型是int
1 << 30 是int取值范围里最大的2的幂的数字
- HashMap默认的负载因子是多少?作用是什么?
默认的负载因子是0.75f
作用:数组长度*负载因子得到阈值,元素个数达到阈值后就扩容
- HashMap默认的负载因子为什么是0.75f?
取得了时间和空间的平衡
如果负载因子过小,会导致装载一点点数据就扩容,利用时间,牺牲空间
如果负载因子过大,会导致装载满了才扩容,利用空间,牺牲了时间
HashMap存放null键null值的位置?
hash数组下标为0的位置
HashMap的扩容机制?
元素个数大于等于阈值并且添加元素的下标位置不等于null,才会扩容
扩容的容量是原来的2倍
什么叫做hash回环?
多线程的情况下
线程1不断的添加元素,导致扩容
线程2不断的遍历
线程1扩容期间,单向链表的下一个节点位置有闭环,线程2遍历有出现hash回环的问题
经验:使用HashMap出现hash回环问题,不是HashMap的错误,是程序员应该背的锅,因为HashMap明确表示该实现不是一个线程安全的集合,你在多线程下使用出现的问题应该是你去负责。多线程下应该使用ConcurrentHashMap
HashMap使用的注意事项?
1.不能在多线程下使用
2.key的hashCode不要写死了,不然会出现hash碰撞
JDK1.7和1.8中HashMap的区别?
JDK1.7:
一维数组+单向链表
计算hash值:hashCode() + 散列算法
头插法
JDK1.8:
一维数组+单向链表+平衡二叉树(提高查询效率)
计算hash值:高16 ^ 低16位
尾插法
JDK1.8HashMap什么是从一维数组+单向链表 变为 一维数组+平衡二叉树
数组的长度大于64,并且单向链表的长度大于8,就会从一维数组+单向链表 变为 一维数组+平衡二叉树
平衡二叉树小于6时,有会从一维数组+平衡二叉树 变为 一维数组+单向链表
JDK1.8HashMap 为什么单向链表大于8后变为平衡二叉树
因为泊松分布