这一块主要整理一下HashMap与HashTable之间个别重要的点和细节以及这两者之间的异同点!
首先简单介绍一下何谓线程安全?
如果我们的代码所在的进程中有多个线程在同时运行,而且这些线程可能同时运行这段代码。如果每次的运行结果和单线程的运行结果是一样的,而且其他的变量的值也和预期的是一样的,那就是线程安全的。
一、HashMap
1、HashMap线程不安全体现在哪里?
HashMap的默认容量是16,当在进行put的时候,插入的元素超过了容量(由加载因子决定)的范围就会触发扩容操作,也就是rehash过程,这个方法会重新将原数组的内容重新hash到新的扩容数组当中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现在同一数组下用链表表示,造成闭环,导致在获取数据也就是进行get操作的时候,产生死循环,所以HashMap是线程不安全的。(本来应该画图解释的,但是限于自身对这个多线程环境操作不太熟悉,也就直接给出文字总结版)。
2、HashMap的适用场景?
因为HashMap是非线程安全的,故其适用于单线程环境下。
底下的代码演示是上课没写出来,课下自己又练了练,贴在这里。
//10万个数据统计数据重复出现的次数并打印
//首先随机产生10万个原始数据
LinkedList list=new LinkedList();
Random random=new Random();
for(int i=0;i<100000;i++){
list.add(random.nextInt(100));
}
//统计出现的次数
HashMap<Integer,Integer> map=new HashMap<>();
Iterator<Integer> iterator=list.iterator();
while(iterator.hasNext()){
Integer value=iterator.next();
Integer num=map.get(value);
if(num==null){
map.put(value,1);
}else{
map.put(value,num+1);
}
}
//打印数据
System.out.println("统计10万个数据重复出现的次数:");
Iterator<Map.Entry<Integer,Integer>> iterator1=map.entrySet().iterator();
while(iterator1.hasNext()){
Map.Entry<Integer,Integer> entry=iterator1.next();
Integer value1=entry.getKey();
Integer number=entry.getValue();
System.out.print(number+" "+value1 +" ");
}
System.out.println();
}
//统计10万个数据,找出第一个重复的数据并进行打印
//方法一、
//首先随机产生10万个原始数据
LinkedList list=new LinkedList();
Random random=new Random();
for(int i=0;i<100000;i++){
list.add(random.nextInt(100));
}
//统计第一个重复出现的数据
System.out.println("第一个重复的数据为:");
HashMap<Integer,Integer> map=new HashMap<>();
Iterator<Integer> iterator=list.iterator();
while(iterator.hasNext()){
Integer value=iterator.next();//数据本身,也就是键值key的位置
if(!map.containsKey(value)){//HashMap中如果不包含此键值,说明是第一次重复的数据
System.out.println(value+" ");
return ;
}
}
//方法二、
LinkedList list=new LinkedList();
Random random=new Random();
for(int i=0;i<100000;i++){
list.add(random.nextInt(100));
}
System.out.println("第一次重复出现的数据为:");
HashMap<Integer,Integer> map=new HashMap<>();
Iterator<Integer> iterator=list.iterator();
while(iterator.hasNext()){
Integer value=iterator.next();
Integer num=map.get(value);
if(num==null){
map.put(value,1);
}else{
System.out.print(value+" ");
return;
}
}
//统计10万个数据,找出出现次数最多的数据并进行打印
LinkedList list=new LinkedList();
Random random=new Random();
for(int i=0;i<100000;i++){
list.add(random.nextInt(100));
}
Iterator<Integer> iterator=list.iterator();
HashMap<Integer,Integer> map=new HashMap<>();
Integer max=0;
Integer data=0;
while(iterator.hasNext()){
Integer value=iterator.next();
Integer num=map.get(value);
if(num==null){
map.put(value,1);
}else{
map.put(value,num+1);
}
}
System.out.println("统计10万个数据中重复出现次数最多的数据为:");
Iterator<Map.Entry<Integer,Integer>> iterator1=map.entrySet().iterator();
while(iterator1.hasNext()){
Map.Entry<Integer,Integer> entry=iterator1.next();
Integer Data=entry.getKey();
Integer vvalue=entry.getValue();
if(vvalue>max){
max=vvalue;
data=Data;
}
}
System.out.println(data+"==>"+max);
二、HashTable
HashTable在JDK1.1.的时候就已经有了。
1、HashTable的线程安全体现在哪里?
HashTable的put()、get()、remove()方法是关键字synchronized来修饰的,其保证了方法的同步性进而保证了其线程安全性。
synchronized关键字:
加上synchronized的关键字的方法,如果两个线程同时操作该方法,表示这两个过程是并发的。
不加synchronized关键字的方法,如果两个线程同时操作该方法,表示这两个过程是顺次执行的,也就是一个过程执行完再执行另一个过程。
在多线程环境下,synchronized关键字非常常见,当需要进行“同步”操作的时候,需要该关键字对代码块或者方法进行锁定,被synchronized锁定的代码块,只能同时有一条线程访问该代码块。那么,synchronized关键字是锁对象还是锁代码块?答案是锁对象!!!(在这里,直接给出结论,后续可能会使用代码解释该关键字修饰方法的详细解析)
当synchronized修饰静态方法时,表示锁定的是class对象;修饰动态方法时,表示锁定的是当前对象(this)。
当synchronized修饰代码块时,表示锁住的是某个对象,被该关键字修饰的其他线程的方法如果要执行,也必须得到该锁,即synchronized代码块执行完。
2、HashTable的适用场景?
HashTable是早期Java类库提供的一个哈希表表现,本身是同步的,不支持null值和空键,它的方法是同步方法,所带来的问题的无论在什么时刻,只能有一个线程来操作HashTable,效率比较低下,导致的性能开销,现在已经很少使用了。
3、HashTable源码中使用的Entry与HashMap源码中的Entry一样吗?
答案是一样的,源码分析如下:
//HashMap内部定义的静态内部类Entry,实现Map.Entry<K,V>接口
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
根据上述的成员变量可以看出,该Entry是单向链表,只有一个next域指向下一个节点!!!
//HashTable定义的静态内部类Entry,实现Map.Entry<K,V>接口
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry<K,V> next;
根据定义的成员变量可以看出,是单向链表,只含有一个指向下一个节点的next域
4、HashTable使用的迭代器与之前集合实现类所使用的迭代器有何区别?
(Enumeration枚举遍历与Iterator遍历有何区别???)
HashTable使用的迭代器是Enumeration枚举遍历元素的。
Enumeration枚举仅适用于传统,例如HashTable、Vector,枚举是初始Java发行版JDK1.0的一部分,现在已经基本被Iteration迭代器所取代了。区别主要在于:
Enumeration Iteration
只有两个函数接口,通过Enumeration,只能读取 有三个函数接口,除了能读取集合中的数据之外,也可以对集合
集合中的数据,但是不能对数据进行修改。 中的数据进行修改。
不支持fail-fast快速失败机制 支持fail-fast快速失败机制
三、HashMap与HashTable之间的异同点?
1、相同点
HashMap与HashTable都实现了Map接口,底层数据结构都是数组加链表。
2、不同点:主要从继承关系、初始默认容量的大小、线程安全性、扩容方式、null值的处理、hash函数、方法这八个方面来说明。
HashMap HashTable
继承关系: extends AbstractMap extends Dictionary
初始默认容量的大小: 16 11
线程安全性: 非线程安全 线程安全的
扩容方式: resize(2 * table.length);(2的指数) 2倍扩容 newCapacity = oldCapacity * 2 + 1 2倍+1扩容
null值的处理: 最多只允许一个key为null,value可多个为null key和value都不允许为null
hash函数: int h = hashSeed; hashSeed ^ k.hashCode();
if (0 != h && k instanceof String) { 首先判断哈希种子是否为0以及传进的k是否为string
return sun.misc.Hashing.stringHash32((String) k); 如果满足,调用特殊哈希函数处理!
}
h ^= k.hashCode();
方法: 只含有ContainsKey(Object key)方法 含有ContainsKey(Object key)、
Contains(Object value)、 ContainsValue(Object value)