HashMap相关知识,一篇文章学透HasshMap

目录

一.HashMap底层数据结构原理

JDK1.7 HashMap的底层数据结构是数组+链表

JDK1.8 HashMap的底层数据结构是数组+链表+红黑树

二.HashMap的扩容机制

JDK1.7版本扩容步骤

JDK1.8版本扩容步骤

三.HashMap的Put⽅法

HashMap的Put⽅法的⼤体流程:

四.ConcurrentHashMap的扩容机制

1.7版本ConcurrentHashMap的扩容机制

1.8版本ConcurrentHashMap的扩容机制

五.Hashtable 与 HashMap 的区别 

1、两者父类不同

2、对外提供的接口不同

3、对null的支持不同

4、安全性不同

5、初始容量大小和每次扩充容量大小不同

6、计算hash值的方法不同

Hashmap计算hash值的源码:

Hashtable计算hash值的源码:


一.HashMap底层数据结构原理

JDK1.7 HashMap的底层数据结构是数组+链表

1.7中链表插⼊使⽤的是头插法
1.7中哈希算法⽐较复杂,存在各种右移与异或运算

JDK1.8 HashMap的底层数据结构是数组+链表+红黑树

        1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法

        1.8中进⾏了简化,因为复杂的哈希算法的⽬的 就是提⾼散列性,来提供HashMap的整体效率,⽽1.8中新增了红⿊树,所以可以适当的简化哈希 算法,节省CPU资源

二.HashMap的扩容机制

JDK1.7版本扩容步骤

1. 先⽣成新数组
2. 遍历⽼数组中的每个位置上的链表上的每个元素
3. 取每个元素的key,并基于新数组⻓度,计算出每个元素在新数组中的下标
4. 将元素添加到新数组中去
5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性

JDK1.8版本扩容步骤

1. 先⽣成新数组
2. 遍历⽼数组中的每个位置上的链表或红⿊树
3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去
4. 如果是红⿊树,则先遍历红⿊树,先计算出红⿊树中每个元素对应在新数组中的下标位置
a. 统计每个下标位置的元素个数
b. 如果该位置下的元素个数超过了8,则⽣成⼀个新的红⿊树,并将根节点的添加到新数组的对应
位置

三.HashMap的Put⽅法

HashMap的Put⽅法的⼤体流程:

  1. 根据Key通过哈希算法与与运算得出数组下标
  2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放⼊该位置
  3. 如果数组下标位置元素不为空,则要分情况讨论
        a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成                  Entry对象,并使⽤头插法添加到当前位置的链表
        b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node
                ⅰ. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去                            在这个过程中会判断红⿊树中是否存在当前key,如果存在则更新value
                  ⅱ. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过                        尾插法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表                           过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链                         表Node插⼊到链表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于                           8,那么则会将该链表转成红⿊树
                 ⅲ. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,                             如果需要就扩容,如果不需要就结束PUT⽅法

四.ConcurrentHashMap的扩容机制

1.7版本ConcurrentHashMap的扩容机制

1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的
2. 每个Segment相对于⼀个⼩型的HashMap
3. 每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
4. 先⽣成新的数组,然后转移元素到新数组中
5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本ConcurrentHashMap的扩容机制

1. 1.8版本的ConcurrentHashMap不再基于Segment实现
2. 当某个线程进⾏put时,如果发现ConcurrentHashMap正在进⾏扩容那么该线程⼀起进⾏扩容
3. 如果某个线程put时,发现没有正在进⾏扩容,则将key-value添加到ConcurrentHashMap中,然
后判断是否超过阈值,超过了则进⾏扩容
4. ConcurrentHashMap是⽀持多个线程同时扩容的
5. 扩容之前也先⽣成⼀个新的数组
6. 在转移元素时,先将原数组分组,将每组分给不同的线程来进⾏元素的转移,每个线程负责⼀组或多组的元素转移⼯作

五.Hashtable HashMap 的区别 

1、两者父类不同

HashMap 是继承自 AbstractMap 类,而 Hashtable 是继承自 Dictionary 类。不过它们都实现了同时
实现了 map Cloneable (可复制)、 Serializable (可序列化)这三个接口。

2、对外提供的接口不同

Hashtable HashMap 多提供了 elments() contains() 两个方法。 elments() 方法继承自
Hashtable 的父类 Dictionnary elements() 方法用于返回此 Hashtable 中的 value 的枚举。
contains() 方法判断该 Hashtable 是否包含传入的 value 。它的作用与 containsValue() 一致。事实
上, contansValue() 就只是调用了一下 contains() 方法。

3、对null的支持不同

Hashtable key value 都不能为 null
HashMap key 可以为 null ,但是这样的 key 只能有一个,因为必须保证 key 的唯一性;可以有多个
key 值对应的 value null

4、安全性不同

        <1>HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自 己处理多线程的安全问题。
        <2>Hashtable是线程安全的,它的每个方法上都有 synchronized 关键字,因此可直接用于多线程中。
        <3>虽然HashMap 是线程不安全的,但是它的效率远远高于 Hashtable ,这样设计是合理的,因为大部分的使用场景都是单线程。
        <4.1>当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。
        <4.2>ConcurrentHashMap虽然也是线程安全的,但是它的效率比 Hashtable 要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

5、初始容量大小和每次扩充容量大小不同

创建时如果不指定容量初始值Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。

创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小。

6、计算hash值的方法不同

Hashmap计算hash值的源码:

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

注:key值的hash值的计算方法为:先调用hashCode方法计算出来一个hash值,再将hash与右移16位后相异或,从而得到新的hash值

Hashtable计算hash值的源码:

public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

注:Hashtable通过计算key的hashCode()**来得到hash值就为最终hash值。



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
日期:2023年2月1日 今天是我在ABC公司的第一天实习。我被分配到一个项目组,他们正在开发一个基于Java的Web应用程序。他们的代码库中使用了大量的HashMap数据结构。在今天的工作中,我学习了HashMap的基本概念和用法。 HashMapJava中非常常用的数据结构之一,它是一个键值对的映射表,可以通过键来快速访问值。HashMap的特点是:插入、删除、查找元素的时间复杂度都是O(1)的。这也是它在Java编程中非常受欢迎的原因之一。 在项目中,HashMap被用于缓存数据,以提高Web应用程序的性能。具体来说,我们有一个数据源,它包含了数万条记录,我们需要从中检索特定的数据。为了避免每次查询都要访问数据源,我们将查询结果存储在HashMap中,下次查询时可以直接从HashMap中获取结果,而不必访问数据源。这样可以大大减少查询时间,提高应用程序的响应速度。 在学习HashMap的过程中,我发现它是基于哈希表实现的。哈希表是一种通过计算关键字的哈希值来访问记录的数据结构。在Java中,HashMap使用键的哈希值作为索引来访问值。当插入一个键值对时,HashMap会首先计算键的哈希值,然后将键值对存储在对应的哈希桶中。如果两个键的哈希值相同,它们将被存储在同一个哈希桶中,以链表的形式存储。如果链表的长度超过了一定的阈值,链表将被转换为红黑树,以提高查询效率。 在学习中,我还了解了HashMap的一些注意事项。首先,HashMap不是线程安全的,如果多个线程同时访问同一个HashMap,可能会导致数据不一致。因此,我们需要在多线程环境下使用ConcurrentHashMap,它是线程安全的HashMap实现。其次,当HashMap的容量超过了一定阈值时,它会进行扩容,这可能会导致性能下降。因此,在使用HashMap时,我们需要根据实际情况设置合适的容量和负载因子。 今天的学习让我对HashMap有了更深入的理解,我也开始尝试在项目中使用HashMap来提高应用程序的性能。我相信在未来的实习中,我还会遇到更多有趣的Java技术和挑战。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值