数据结构 Hash表(哈希表)

hash表的查找

查找过程和造表过程一致,假设采用开放定址法处理冲突,则查找过程为: 对于给定的key,计算hash地址index =
H(key),hashcode相同,对象不一定相同,取决于hash算法,这里的对象指的是key 如果数组arr【index】的值为空
则查找不成功 如果数组arr【index】== key 则查找成功 //相当于符合equals方法 否则
使用冲突解决方法求下一个地址,直到arr【index】== key或者 arr【index】==null

当两个对象的hashcode相同会发生什么?

hashcode相同,说明两个对象HashMap数组的同一位置上,接着HashMap会遍历链表中的每个元素,通过key的equals方法来判断是否为同一个key,如果是同一个key,则新的value会覆盖旧的value,并且返回旧的value。如果不是同一个key,则使用冲突解决方法求下一个地址,直到arr【index】==
key或者 arr【index】==null

HashTable与HashMap的区别

对Null key 和Null value的支持不同
Hashtable中key和value都不允许为null,遇到null,直接返回 NullPointerException
HashMap中key和value都允许为null,遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理

初始容量大小和每次扩充容量大小的不同
Hashtable中hash数组默认大小是11,扩充方式是old*2+1 HashMap中hash数组的默认大小是16,而且一定是2的指数

计算hash值的方法不同
Hashtable 直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数法来获得最终的位置。
HashMap 中使用键对象来计算hashcode值

线程安全性不同
Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步

HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。具体的原因在下一篇文章中会详细进行分析。使用HashMap时就必须要自己增加同步处理,

虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。这样设计是合理的。在我们的日常使用当中,大部分时间是单线程操作的。HashMap把这部分操作解放出来了。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

HashMap MAXIMUM_CAPACITY 为什么设置成1 << 30

1.HashMap在确定数组下标Index的时候,采用的是( length-1) & hash的方式,只有当length为2的指数幂的时候才能较均匀的分布元素。 所以HashMap规定了其容量必须是2的n次方

2.由于HashMap规定了其容量是2的n次方,所以我们采用位运算<<来控制HashMap的大小。 使用位运算同时还提高了Java的处理速度。HashMap内部由Entry[]数组构成,Java的数组下标是由Int表示的。所以对于HashMap来说其最大的容量应该是不超过int最大值的一个2的指数幂,而最接近int最大值的2个指数幂用位运算符表示就是 1<< 30

Hashmap resize方法

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

从代码中可以看到,如果原有的 table 达到了上限,就不能在继续扩容

如果还未达到上限就会调用 transfer 方法

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;
            // 如果生成了hashseed 就重新进行hash
            // 基本上默认 false 
            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;
        }
    }
}

下面是对transfer的理解(图解):
一开始
一开始在桶头部插入A,第二次也是在同一个桶的头部插入B,形成倒插

resize:
hashmap当链表长度大于 8 并且 数组的长度 大于 64 的时候会转化红黑树!!!!
如果链表长度大于8但是数组的长度小于64,则会选择扩容resize(),而不会转红黑树

为什么HashMap链表会形成死循环

准确的讲应该是 JDK1.7 的 HashMap 链表会有死循环的可能,因为JDK1.7是采用的头插法,在多线程环境下有可能会使链表形成环状,从而导致死循环。JDK1.8做了改进,用的是尾插法,不会产生死循环。

HashMap和LinkedHashMap的区别

具体看这篇文章:

https://www.cnblogs.com/lijingran/p/9073090.html


原文链接:https://blog.csdn.net/qq_41965731/article/details/109565811
原文链接:https://blog.csdn.net/wangxing233/article/details/79452946
原文链接:https://blog.csdn.net/u011109881/article/details/80379505

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值