深入理解HashMap

深入理解hashMap

hashMap简介

HashMap是一种以K-V存储的集合对象,结合了数组结构查询快和链表结构插入、删除快的特点;它允许key、value为null
是一种无序并且线程不安全的集合对象。

hashMap初始化

hashMap的实例有两个参数影响其性能:”初始容量”和”加载因子”。容量是哈希表中桶的数量,初始容量知识哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的元素数超出了加载因子与当前容量的乘积时,就要对这个哈希进行rehash,从而哈希表将具有两倍左右的桶数。默认的加载因子为0.75,这是在速度和空间上的折中,加载因子过高减少空间但也增加了查询速度。在设置初始容量时应该考虑元素的总数,以此来减少rehash的次数。当初始容量大于总元素数除以加载因子时就不会发生rehash。

// 默认构造函数。
HashMap()

// 指定“容量大小”的构造函数
public HashMap(int initialCapacity)

// 指定“容量大小”和“加载因子”的构造函数
public HashMap(int initialCapacity, float loadFactor)

// 包含“子Map”的构造函数
public HashMap(Map<? extends K, ? extends V> m) 

我们来看一下第三个构造函数代码:

    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))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }
    void init() {
    }

我们可以发现传入了参数并没有给我们初始化,那是不是就说明我们传入的参数是无效的呢?其实这一点是hashMap设计聪明的地方,如果初始化一个hashMap,而不用岂不是占用了空间资源,实际上当我们调用了put方法便会实现初始化。

    public V put(K key, V value) {
    //table并没有改变,这里为true
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);
        //把threshold设置为最小大于输入容量的2的N次放
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

这里会把threshold设置为最小的大于输入容量的2的N次方,这也是之前提的,当rehash时,大约为2倍而不是正好2倍就在于次。而这样做也是另一个hashMap聪明的地方。
前面提到过hashMap的数据结构是数组和链表的结合,所以要尽量让元素分布的均匀一些,我们首先想到的便是取模,但是这样做运算消耗比较大,而它是这样做的:

    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);
    }

这样做的好处就相当于对它进行取模,具体的大家可以进行一下测试,当数组长度为2的N次方和非2的N次方时的对比,会发现当为2的N次方时分布更加均匀。

hash碰撞

解决hash冲突有拉链法、开放地址法。而hashMap解决hash碰撞使用的是拉链法,当我们往hashMap中put元素的时候,先根据key的hash值,找到这个元素在数组中的位置,然后把它放到对应的位置中。如果当前元素所在的位置上已经有其他元素,则这个位置将会以链表的形式存放,后加入的放在链头,先加入的放在链尾。当需要从hashMap获取元素的时候,首先通过key的hash值找到数组中对应的位置,然后通过key的equals方法在对应位置的链表中找到需要的元素。

hashMap结构图

结构图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值