目录
🐇今日良言:投资自己才是最好的投资
🐉一.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 中的 keyV getValue () 返回 entry 中的 valueV 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博主在后面的文章会有具体介绍,后续会附上博客链接)
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不支持并发操作