目录
5.1扩容方法inflateTable(threshold)
5.3indexFor(int h, int length)
5.4扩容addEntry(hash, key, value, i);
1.散列表简介
1)什么是哈希表:
由数组和链表组合成的一种根据关键码值(Key value)而直接进行访问的数据结构
2)特点(优点):
结合了数组和链表的优点
数组的特点是:寻址容易,插入和删除困难;
而链表的特点是:寻址困难,插入和删除容易。
3)结构:
4)缺点:
数组创建后难于扩展
5)散列法
元素特征转变为数组下标的方法就是散列法。三种比较常用的:
1,除法散列法
2,平方散列法
3,斐波那契(Fibonacci)散列法
上图的案例除法散列法 :
index = value % 16 (求模)
例如:12,28,108,140除16的模都是12所以存储在数组12的位置
6)散列冲突:
如上图的12,28,108,140除16的模都是12所以存储在数组12的位置这就是哈西冲突
2.HashMap的数据结构
维护Hashmap的内部的是一个Entry<K,V>,数据结构是一个单链表。源码如下:
static class Entry<K,V> implements Map.Entry<K,V> { |
单项链表
操作方式:
头插法(不用遍历链表直接在头插入)
尾差发(需要遍历链表找到最后一个指针在插入)
3主要参数
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认容量16 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2^30; static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子,扩容的依据 static final Entry<?,?>[] EMPTY_TABLE = {}; transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; transient int size; //map大小 final float loadFactor; int threshold; // 第一次为初始容量之后为总容量的75%,当size大于threshold进行扩容 transient int modCount; //修改次数 |
4.HashMap的核心构造方法
public HashMap(int initialCapacity, float loadFactor) {
|
4.1初始化容量
initialCapacity(初始化容量默认16,最大为2^30)loadFactor(负载因子0.75f) 初始化时赋值threshold为初始容量(之后为总容量的75%) |
5.HashMap的Put方法
public V put(K key, V value) { //计算key hash值在table中的位置 返回h & (length-1); //因为length的大小为2^n所以(length-1)保证了每一位都有1,否则1&0;的结果为0,这样8和9返回的位置就相同了 int i = indexFor(hash, table.length); //计算桶位
|
5.1扩容方法inflateTable(threshold)
private void inflateTable(int toSize) { //确保HashMap的容量为2的N次幂(取离初始容量最近比较大一段的2的N次幂) //创建Entry初始容量默认为16 |
5.1.1初始容量算法
private static int roundUpToPowerOf2(int number) {
|
解释:Integer.highestOneBit(int i)的作用
如果一个数是0, 则返回0;
如果是负数, 则返回 -2147483648:【1000,0000,0000,0000,0000,0000,0000,0000】(二进制表示的数);
如果是正数, 返回的则是跟它最靠近的比它小的2的N次方
roundUpToPowerOf2方法返回的为Integer.highestOneBit((number - 1) << 1) |
5.2 putForNullKey(value);
private V putForNullKey(V value) { //把Null保存在table[0]位置
|
当key为null,调用putForNullKey方法,保存null于table第一个位置中,这是HashMap允许为null的原因
5.3indexFor(int h, int length)
static int indexFor(int h, int length) {
|
计算key hash值在table中的位置返回h & (length-1); //因为length的大小为2^n所以(length-1)保证了每一位都有1,否则1&0;的结果为0,这样8和9返回的位置就相同了
5.4扩容addEntry(hash, key, value, i);
每次扩大到原来空间的2倍
void addEntry(int hash, K key, V value, int bucketIndex) { //每次创建一个Entry<K,V>后size加1,空间不足threshold(总空间的75%),扩大长处的2倍
|
5.4.1 创建Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
|
获取bucketIndex处的Entry
将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
size加1
解释:
单链表头插法
第一次存储时例如 bucketIndex=1;则 Entry<K,V> e= table[bucketIndex]为空即e为空
table[bucketIndex] = newEntry<>(hash, key, value, e);可以理解成
table[1] = new Entry<>(hash, key,value, null);
第二次Entry<K,V> e= table[bucketIndex]为空即e为 table[1]的地址
4.5Put方法综合分析图
6.HashMap的get方法
public V get(Object key) {
|
6.2getEntry(key);
final Entry<K,V> getEntry(Object key) { //先定位到桶位,在进行链表的遍历(indexFor()方法上面分析了)
|
//如果hash冲突了,key是不同的可以通过遍历链表找到value
7总结
1. 维护Hashmap内部的是一个Entry,Hashmap的默认容量为16,负载因子为0.75,
2. Entry的默认初始容量为16(Entry初始容量一定为2的N次幂,Hashmap初始容量不满足阔大到最近较大的一个2的N次幂)
3. 扩容方式当Entry已用空间>=总空间的75%,总空间扩大到原来的2倍。
4. HashMap可以存Null值,存在table[0]处
5. HashMap的容量指的是Entry<K,V>[] table的长度
6. 计算桶位的方式 h & (length-1);
计算key hash值在table中的位置返回h & (length-1); //因为length的大小为2^n所以(length-1)保证了每一位都有1,否则1&0;的结果为0,这样8和9返回的位置就相同了