HashTable HashMap ConcurrentHashMap 的介绍以及区别

目录

🐇今日良言:投资自己才是最好的投资

🐉一.HashMap.

🐕二.HashTable

🐍三.ConcurrentHashMap

🐂四.三者的区别


🐇今日良言:投资自己才是最好的投资

这篇博客主要介绍的是 HashTable HashMap ConcurrentHashMap 之间的区别,重点要知道ConcurrentHashMap的优点(好处/与HashTable相比的优点)

🐉一.HashMap

1.概念和场景

在介绍HashMap之前,先来介绍一下什么是Map:

Map是一种专门用来搜索的容器或者数据结构,其具体的搜索效率与其具体的实例化子类有关.

在我们之前的学习过的内容中,常见的搜索方式有:

1).直接遍历  时间复杂度为O(N),元素较多时,效率会非常慢

2).二分查找  时间复杂度为O(logN),但搜索前必须要求序列是有序的

上述这两种方式都适合静态类型的查找,即:一般不会对区间进行插入和删除操作.而现实中的很多查找,如:

1).根据姓名查找成绩

2).通讯录,即根据姓名查询联系方式

3).不重复集合,即需要先搜索关键字是否已经在集合中

可能在查找时进行一些插入和删除的操作,也就是动态查找,此时上述的两种查找方式就不适合了,而Map是一种适合动态查找的集合容器

2.模型

一般把搜索的数据叫做关键字(Key),和关键字对应的称为值(Value),将其称为 Key-Value键值对,所以,模型一般有两种:

1).纯Key模型

    比如:

    有一个英文字典,快速查找一个单词是否在词典中

    快速查找某个名字在不在通讯录中

2).Key-Value模型

    比如:

     梁山好汉的江湖绰号(每个好汉都对应一个绰号)

Map中存储的就是Key-Value的键值对,Set中只存储了Key

 3.关于Map的说明

Map是一个接口类,没有继承自Collection,该类中存储的是<K,V>键值对,并且K一定是唯一的,而且不能重复.

关于Map.Entry<K,V> 的说明

Map.Entry<K,V> 是map内部实现的用来存放<Key,Value>键值对映射关系的内部类,该内部类中主要提供提供了<key,value>的获取,value的设置以及Key的比较方式

方法                                        解释
K getKey ()                     返回 entry 中的 key
V getValue ()                  返回 entry 中的 value
V setValue(V value)       将键值对中的 value 替换为指定 value
通过下面的代码以及运行结果增加理解

public class Exercise {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
         map.put("宋江","及时雨");
         map.put("鲁智深","花和尚");
         Set<Map.Entry<String,String>> set = map.entrySet();
         for (Map.Entry<String,String> entry:set) {
            System.out.println("K:"+entry.getKey()+"  V:"+entry.getValue());
         }
    }
}

public class Exercise {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
         map.put("宋江","及时雨");
         map.put("鲁智深","花和尚");
         Set<Map.Entry<String,String>> set = map.entrySet();
        for (Map.Entry<String,String> entry:set) {
            entry.setValue("好人");
        }
        for (Map.Entry<String,String> entry:set) {
            System.out.println("K:"+entry.getKey()+"  V:"+entry.getValue());
        }
    }
}

注:Map.Entry<K,V>并没有提供设置Key的方法

 4.Map的常用方法说明

注: 

1).Map是一个接口,不能直接实例化对象,如果要实例化对象,只能实例化其实现类TreeMap或者HashMap

2).Map中存放的键值对的Key是位移的,Value可以重复

3).Map中的Key可以全部分离出来存储到Set中(Key值不重复)

4).Map中的Value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)

5).Map中键值对的Key不能直接修改,Value可以修改,如果要修改Key,只能先将Key删除,然后再重新进行插入

5.HashMap的使用案例

public class Exercise {
    public static void main(String[] args) {
            Map<String, String> m = new HashMap<>();
    // put(key, value):插入key-value的键值对
    // 如果key不存在,会将key-value的键值对插入到map中,返回null
            m.put("林冲", "豹子头");
            m.put("鲁智深", "花和尚");
            m.put("武松", "行者");
            m.put("宋江", "及时雨");
            String str = m.put("李逵", "黑旋风");
            System.out.println(m.size());
            System.out.println(m);
    // put(key,value): 注意key不能为空,但是value可以为空
    // key如果为空,会抛出空指针异常
    //m.put(null, "花名");
            str = m.put("无名", null);
            System.out.println(m.size());
    // put(key, value):
    // 如果key存在,会使用value替换原来key所对应的value,返回旧value
            str = m.put("李逵", "铁牛");
    // get(key): 返回key所对应的value
    // 如果key存在,返回key所对应的value
    // 如果key不存在,返回null
            System.out.println(m.get("鲁智深"));
            System.out.println(m.get("史进"));
    //GetOrDefault(): 如果key存在,返回与key所对应的value,如果key不存在,返回一个默认值
            System.out.println(m.getOrDefault("李逵", "铁牛"));
            System.out.println(m.getOrDefault("史进", "九纹龙"));
            System.out.println(m.size());
    //containKey(key):检测key是否包含在Map中,时间复杂度:O(logN)
    // 按照红黑树的性质来进行查找
    // 找到返回true,否则返回false
            System.out.println(m.containsKey("林冲"));
            System.out.println(m.containsKey("史进"));
    // containValue(value): 检测value是否包含在Map中,时间复杂度: O(N)
    // 找到返回true,否则返回false
            System.out.println(m.containsValue("豹子头"));
            System.out.println(m.containsValue("九纹龙"));
    // 打印所有的key
    // keySet是将map中的key防止在Set中返回的
            for(String s : m.keySet()){
                System.out.print(s + " ");
            }
            System.out.println();
    // 打印所有的value
    // values()是将map中的value放在collect的一个集合中返回的
            for(String s : m.values()){
                System.out.print(s + " ");
            }
            System.out.println();
    // 打印所有的键值对
    // entrySet(): 将Map中的键值对放在Set中返回了
            for(Map.Entry<String, String> entry : m.entrySet()){
                System.out.println(entry.getKey() + "--->" + entry.getValue());
            }
            System.out.println();
        }
}

 运行结果: 

🐕二.HashTable

HashTable 继承了Dictionary抽象类,是一个Dictionary抽象类的具体实现,Dictionary是声明了操作"键值对"的函数接口的抽象类

哈希表的代码实现博主之前写过博客:

(4条消息) 哈希表(限定版)_程序猿小马的博客-CSDN博客

HashTable  定义了四种构造方法

1).默认构造方法

   Hashtable

2).传入一个参数,指定哈希表的大小

    Hashtable(int size)

3).传入两个参数,第一个参数指定哈希表的大小,第二个参数指定负载因子(即:达到负载因子,哈希表中就不能再添加元素,需要扩容)

    Hashtable(int size,float fillRatio)

4).创建了一个以M中元素为初始化元素的哈希表。

哈希表的容量被设置为M的两倍。

    Hashtable(Map m)

HashMap除了从Map接口中定义的方法外,还包含以下方法:

🐍三.ConcurrentHashMap

  ConcurrentHashMap是更优化的线程安全哈希表,主要使用于多线程中

  对于ConcurrentHashMap的详细介绍放到下面与HashTable区别中

🐂四.三者的区别

1.HashTable 和 ConcurrentHashMap的区别

1).ConcurrentHashMap相较于HashTable 大大缩小了锁冲突的概率,具体来讲就是将一把大锁转换成了多把小锁.

   HashTable的做法是在方法上直接加synchronized,等于是给this加锁.只要操作哈希表上的任意元素,都会产生加锁,也就都可能发生锁冲突.

   ConcurrentHashMap的做法:让每条链表有各自的锁(而不是公用一把大锁),具体来说就是:使用每个链表的头结点作为锁对象.(在jdk1.8之前ConcurrentHashMap使用的是"分段锁",也就是是给几条链表加锁,从而实现缩小锁冲突的概率,但是这种做法不够彻底,一方面粒度切分的还不够细,另一方面代码实现也更繁琐)

2).ConcurrentHashMap做了一个激进的操作,针对读操作不加锁,针对写操作加锁.

     具体来说就是:

     a.两个线程如果都进行读取变量的操作,不发生冲突(读和读操作之间没有冲突)

     b.两个线程如果都进行修改变量的操作,发生冲突(写和写之间发生冲突)

     c.如果一个线程进行读取变量的操作,一个线程进行修改变量的操作,不发生冲突.

     第三种情况中,必须要求写操作是原子的,并且变量是被voltile修饰的,否则就会发生类似"脏读"的情况

    脏读属于数据库事务的知识点,博主之前的博客有过详细介绍:

  (4条消息) 如何理解数据库事务?_程序猿小马的博客-CSDN博客

   volatile关键字,博主在之前的博客也有过详细介绍,主要是解决内存可见性问题和禁止指令重排序

    (4条消息) 线程安全问题_程序猿小马的博客-CSDN博客

3).ConcurrentHashMap内部充分的使用了CAS,通过这个进一步的削减加锁操作的数目

    (CAS博主在后面的文章有具体介绍,如下是博客链接)

CAS 和 Synchronized优化过程以及常见的锁策略_程序猿小马的博客-CSDN博客

4).二者对于扩容的方式不同

    HashTable/HashMap 扩容:

    创建一个更大的数组空间,将旧数组上的每条链表上的每个元素都搬运到新的数组上,主要进行的操作就是插入和删除,这个扩容操作会在某次put操作的时候发生,也就是达到负载因子时,此时,如果旧数组中的元素特别多的话,就导致这次的put操作比平常的put操作要慢很多倍

     ConcurrentHashMap 扩容:

     采用的是"化整为零"的方式,也就是每次搬运一小部分

     创建新的数组,旧数组也保留.

     每次put操作,都往新数组上添加,同时进行一部分的搬运(将一部分旧数组上的元素搬运到新的数组上)

     每次get的时候,新数组和旧数组都查询.

     每次remove的时候,直接删除即可

     经过一定时间之后,所有的元素都搬运好了,此时就释放旧数组

2.HashTable和HashMap的区别

1).父类不同

 HashTable的父类是Dictionary,HashMap的父类是AbstractMap

2).线程安全

  HashTable是线程安全的,HashMap是线程不安全的

  这就意味着,在单线程下HashMap的性能要比HashTable要好

  如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。

3.HashMap和ConcurrentHashMap的区别

 1).线程安全

     ConcurrentHashMap是线程安全的,而HashMap是线程不安全的

2).并发操作

     ConcurrentHashMap支持并发操作,HashMap不支持并发操作

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值