大话HashMap

HashMap的概要
HashMap基于哈希表的Map接口的实现。
允许null值,bull键。
不同步,线程不安全。
想要获得线程安全的HashMap可以用Collections的静态方法synchronizedMap方法。
HashMap的底层设计
底层主要使用数组和链表实现的。
因为它实际算散列码来决定存储位置,所以,查询速度相当快。
HashMap主要通过key的hashCode来计算hash值,只要hashCode值相同,计算出来的hash值就是一样的。
如果存储对象多了,可能引起hash值相同,这就是所谓的hash冲突,HashMap底层是通过链表来解决hash冲突。
hash链表的每个元素都横向产生了一个单链表,如果key映射到了链表同一位置,那就放入单链表中。
先比较hashCode值,然后equals比较,一样的话覆盖,不一样则添加到单链表的头,所以最近加入的最容易被访问。
HashMap底层数组的维护对象Entry

public class Entry<K,V> implements Map.Entry<K,V> {
    final K key;
    V value;
    Entry<K,V> next;
    final int hash;

    Entry(int h,K k,V v,Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
}

HashMap的构造函数
构造函数的两个参数,一个是默认的初始化容量,另一个是默认的负载因子。
负载因子:就是Map实际存放对象的个数和它所申请的空间大小的比值,如果超过这个比值,Map就会自动扩容,自动扩容的时候以2的指数的方式进行扩容。
放置对象我们要Entry空间,但是我们只在put方法后才创建,用到才创建,所以叫做懒汉法。

/*几个重要的参数,常看见的
 *aka 16默认初始化的大小
 *static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
 *默认负载因子
 *static final float DEFAULT_LOAD_FACTOR = 0.75f;
 *扩容的临近值
 *int threshold;
 */
//HashMap第一种构造方法,不带参数,默认值16,0.75
public HashMap() {
    this(DEFAULT_INITIAL,DEFAULT_LOAD_FACTOR);
}
//HashMap的第二种构造方法,自己定义Map长度,0.75
public HashMap(int initialCapacity) {
    this(initialCapacity,DEFAULT_LOAD_FACTOR);
}
//HashMap的第三种构造方法,这跟的是Map空间和时间效率
public HashMap(int initialCapacity,float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +  initialCapacity);
    // 在Map中也定义了容量的最大值,跟ArrayList一样
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        this.loadFactor = loadFactor;
        // threshold 为临界值,当集合中元素个数达到时就进行2的指数倍扩容
        threshold = initialCapacity;
        init();
    }
// 将整个集合添加到Map中, 这个过程我们好好分析一下,首先传进来有个Map, 我们可以看到它之后调用了上面的带两个参数的构造函数HashMap(int,float),第一个数据是 (程序定义的最大容量)和(map的容量/负载因子 +1)中的最大值,比如m.size()=21, 那么初始的容量就为 21/0.75+1=29, 执行HashMap(29,0.75), 临界值 threshold =29;
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);
    // 将m创建到新的Map中来
    putAllForCreate(m);
}
// 扩容函数,为什么数组的长度必须定义为2的整数次幂呢,原因在后面
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize 首先找到一个数据是 2的幂次方 而且 大于toSize, 传进来的是29 意味着得到的capacity 是32
    int capacity = roundUpToPowerOf2(toSize);
    // 修改扩容的临界值,根据传进来的值,临界值为24,
    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    // 申请一个32 个空间大小的Entry数组,table就是一个Entry类型的数组
    table = new Entry[capacity];
    // 初始化需要的hash种子
    initHashSeedAsNeeded(capacity);
    } 
}

增加了对象的put()方法
这个会引发很多问题。
比如如果Key值为空的时候,它会直接将放到数组下标为0的位置,并没有计算hash值,这个地址计算有关,所有的Null键都是放在数组中的下表为table[0]的位置的。
如果key值不为空,计算key对象的hashCode()值,然后防置。
数组长度要为2的整数次幂的原因就是数组下标确定的时候的返回值,return h&(length-1);
扩容
HashMap是2的整数次幂扩容,ArrayList是1.5.
为什么HashMap不能保持元素的顺序
a):插入是对元素进行哈希处理,不同元素分配到不同位置
b):容量扩展的时候进行了hash处理
c):复制原表内容的时候链表倒置
get(key)方法和remove(key)方法
显示判断getEntry(key)的结果是不是null,返回boolean值。
clear()方法
这里就不用一个个删除节点,而是一起全部删了
快速失败机制
不是线程安全,在使用迭代器过程中修改了map,那么就会抛出异常,这就是快速失败策略。
本文来自给于

https://blog.csdn.net/woshiluoye9/article/details/56667810

猜你喜欢

转载自blog.csdn.net/qq_42784105/article/details/82661994
今日推荐