Map集合以及它的子类之间的区别关系
一. 概述
Map集合,即Mapping的缩写,采用数学里面的一 一映射原理,计算机术语叫做键值对,即每一个key对应一个value,因此他是集合里面每次存储两个数据的(key和对应的value),即称作为双列集合。
下面会详细介绍两个Map(HashMap ,HashTable)的区别
二. HashMap &&HashTable之间的区别
1.最重要的:线程安全(切记,面试点)
Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步。
HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。具体的原因在后续的博客中进行多线程安全的分析。使用HashMap时就必须要自己增加同步处理,
虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。
2.父类不同
背景
Hashtable是java一开始发布时就提供的键值映射的数据结构,而HashMap产生于JDK1.2。虽然Hashtable比HashMap出现的早一些,但是现在Hashtable基本上已经被弃用了。而HashMap已经成为应用最为广泛的一种数据类型了。造成这样的原因一方面是因为Hashtable是线程安全的,效率比较低。另一方面可能是因为Hashtable没有遵循驼峰命名法吧…也有地方称Hashtable是1.0版双列集合
HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口
3.方法个别不同
Hashtable比HashMap多提供了elments() 和contains() 两个方法。
elments() 方法继承自Hashtable的父类Dictionnary。
elements() 方法用于返回此Hashtable中的value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。
事实上,contansValue() 就只是调用了一下contains() 方法,而contansValue()两者都有这个方法,个人感觉多一个contains()方法多余了。
4. 对Null key 和Null value的支持不同
Hashtable既不支持Null key也不支持Null value。Hashtable的put()方法的注释中有说明。
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
Map<String ,String> hashmap = new HashMap<>();
hashmap.put(null,"一");
hashmap.put(null,"二");
Set<Entry<String ,String>> entrySet = hashmap.entrySet();
Iterator<Entry<String ,String>> iterator2 = entrySet.iterator();
while (iterator2.hasNext()){
Entry<String, String> entry2 = iterator2.next();
System.out.println(entry2);
System.out.println(entry2.getKey() + " " + entry2.getValue());
5.初始容量大小和每次扩充容量大小的不同
Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。
HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
6.分析为什么Hashtable没有HashMap查询速度
1.当然最重要的是多线程的区别(前面说过了,这里就不重复了)
2.两者计算hash不同:
为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。
Hashtable直接使用对象的hashCode。hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。然后再使用除留余数发来获得最终的位置。
Hashtable在计算元素的位置时需要进行一次除法运算,而除法运算是比较耗时的。
HashMap为了提高计算效率,将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高,而计算位运算
为了解决这个问题,HashMap重新根据hashcode计算hash值后,又对hash值做了一些运算来打散数据。使得取得的位置更加分散,从而减少了hash冲突。当然了,为了高效,HashMap只做了一些简单的位处理。从而不至于把使用2 的幂次方带来的效率提升给抵消掉
3.存放数据方式不同
补充:哈希表的介绍
哈希表(散列表):是由数组+链表的组成的
HashMap每个哈希桶(也就是每个数组存放链表结构的位置)里面存放的链表长度超过8的时候,就会自己改变结构,变成红黑树结构,这样查询效率便会更加高效,然后当去除数据,即链表长度小于6的时候(注意此时还是红黑树结构),红黑树就会自动还原成链表结构
1道比较坑的面试题:当HashMap某个哈希桶中链表长度7时,再在这个哈希桶中添加1个数据,此时这个哈希桶中数据结构会有什么变化?
答:如果此时是红黑树,则数据结构不变;如果此时是链表结构,则会变成红黑树结构。
众所周知:二叉树(红黑树)查询数据效率比链表查询效率高效得多,所以当一个哈希桶中数据较多时,HashMap效率明显优于HashTable
3.TreeMap(Java集合之Set(二)-TreeSet补充)
(1).底层原理
在博文:Java集合之Set(二)中有提到,TreeSet的实现其实引用了TreeMap,唯一的区别在于,TreeMap是双列集合,键不能为null,而TreeSet则是单列集合,只需要存储TreeMap的key就可以了,就能实现它的单列集合,有序,不重复的效果了。
TreeMap实现了SotredMap接口,它是有序的集合。而且是一个红黑树结构,每个key-value都作为一个红黑树的节点。如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。
(2).排序
而一般用到TreeMap,就是它的排序特点了
TreeMap提供了四个构造方法,实现了方法的重载。无参构造方法中比较器的值为null,采用自然排序的方法,如果指定了比较器则称之为定制排序.
自然排序:TreeMap的所有key必须实现Comparable接口,所有的key都是同一个类的对象
定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。
详情参照:专门讲述TreeMap的底层原理实现
3.Map集合的输出
输出方式有两种:都是通过Map集合转换成Set集合
package com.kaikeba.practice0925;
import java.util.*;
import java.util.Map.*;
public class PracticeMap {
public static void main(String[] args) {
Hashtable<String ,String> hashtable = new Hashtable<>();
hashtable.put("1","一");
hashtable.put("2","二");
Set<String> keySet = hashtable.keySet();
Iterator<String> iterator = keySet.iterator();
System.out.println("输出方式1:");
while (iterator.hasNext()){
String i = iterator.next();
System.out.println(i + "" + hashtable.get(i));
}
System.out.println();
System.out.println("输出方式2:");
Map<String ,String> hashmap = new HashMap<>();
hashmap.put("1","一");
hashmap.put(null,"二");
Set<Entry<String ,String>> entrySet = hashmap.entrySet();
Iterator<Entry<String ,String>> iterator2 = entrySet.iterator();
while (iterator2.hasNext()){
Entry<String, String> entry2 = iterator2.next();
System.out.println(entry2);
System.out.println(entry2.getKey() + " " + entry2.getValue());
}
}
}
输出: