Java 数据结构5:Hash详解

本文详细探讨了哈希表的概念及其在Java中的实现,尤其是HashMap。通过哈希函数减少冲突,并介绍了开放地址法和链地址法解决冲突的方法。在HashMap 1.8中,引入了红黑树以优化大量冲突时的性能。此外,文章还强调了初始化HashMap大小的重要性,以避免频繁的扩容操作,并提示在并发环境下使用ConcurrentHashMap。
摘要由CSDN通过智能技术生成

哈希表

哈希表也称散列表(Hash),Hash表是基于健值对(key - value)直接进行访问的数据结构。但是他的底层是基于数组的,通过特定的哈希函数把key映射到数组的某个下标来加快查找速度,对于哈希表来说,查找元素的复杂度是O(1)

我们来看一下HashMap里面的Hash函数是怎么实现的

static final int hash(Object key) {
    
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//计算位置i
i = (n - 1) & hash

通过hash找位置i的运算,查找位置i的规则是i = (n - 1) & hash。其中n就是数组的长度,由于n是2的幂次,那么n - 1的高位应该全部为0。如果hash值只用自身的hashcode的话,那么i只会和hash的低位做 & 操作。这样一来,index的值就只有低位参与运算,高位毫无存在感,从而会带来哈希冲突的风险。所以在计算key的哈希值的时候,用其自身hashCode值与其低16位做异或操作。这也就让高位参与到index的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题。

冲突

使用hash函数计算,难免会出现地址冲突,即对于不同的key会计算出相同的数组位置,这时怎么解决呢

开放地址法

开发地址法中,若数据项不能直接存放在由哈希函数所计算出来的数组下标时,就要寻找其他的位置。其他位置的寻找有三种方法:线性探测、二次探测以及再哈希法。

链地址法

在哈希表每个单元中设置链表(即链地址法),某个数据项的关键字值还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。其他同样映射到这个位置的数据项只需要加到链表中,不需要在原始的数组中寻找空位。HashMap中就是使用链地址法解决Hash冲突的

 if ((e = p.next) == null) {
    
                        // 如果p的next为空,将新的value值添加至链表后面
                        p.next = newNode(hash, key, value, null);
HashMap(1.8)

构造函数,还是一些简单的初始化过程,不再赘述

public HashMap(int initialCapacity) {
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
    
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
    
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    
        int s = m.size();
        if (s > 0) {
    
            if (table == null) {
     // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值