HashMap、TreeMap、HashTable和LinkedHashMap的联系与区别
在Java中,Map是作为一个顶级接口,构成了集合框架的一个重要分支。本文将演示如何去使用不同的Map类型,因为在JDK中,Map接口具有HashMap、TreeMap、HashTable和LinkdedHashMap四个子接口。
一、Map概述
在JDK中,一共有多达四种Map接口,它们是HashMap、TreeMap、HashTable、LinkedHashMap,它们的使用频率都非常的高。我们用一句话描述它们的最主要特点:
1. HashMap是基于哈希表(hash table)实现,其keys和values都没有顺序,允许key为null,且唯一。
2. TreeMap是基于红黑树(red-black tree)实现,按照keys排序元素,不允许key为null。
3. LinkedHashMap是基于哈希表(hash table)实现,是HashMap的子类,所以通过继承机制,拥有了HashMap的所有特性。而且,它还增加了保持元素插入顺序的特性(按照插入顺序排序元素)。
4. HashTable区别于HashMap的地方只有,它是同步的(synchronized),并因此性能较低些。不允许有 null 值。为了性能,在线程安全的代码中,优先考虑使用HashMap。
二、HashMap的键约束
HashMap底层是基于哈希表实现的,而哈希表对于键值有约束。当使用自定义的类型来做HashMap的键时,为了程序的运行结果正确,必须定义该类型的equals()方法和hashCode()方法。
1 class Dog {
2 String color;
3
4 Dog(String color) {
5 this.color = color;
6 }
7
8 public String toString() {
9 return color + " dog";
10 }
11 }
12
13 public class Main {
14 public static void main(String[] args) {
15 HashMap<Dog, Integer> dogMap = new HashMap<Dog, Integer>();
16
17 dogMap.put(new Dog("red"), 10);
18 dogMap.put(new Dog("black"), 15);
19 dogMap.put(new Dog("white"), 5);
20 dogMap.put(new Dog("white"), 20);
21
22 System.out.println(dogMap.size());
23 for (Entry<Dog, Integer> entry : dogMap.entrySet()) {
24 System.out.println(entry.getKey(),toString() + " - " + entry.getValue());
25 }
26 }
27 }
此代码片段的输出结果如下所示:
1 4
2 white dog - 5
3 black dog -15
4 red dog - 10
5 white dog - 20
可以发现,我们此处错误地添加了两个“white dogs”到dogMap集合中,但是,dogMap却没有报告错误。实际上,我们希望只能够添加一个“white dogs”到该dogMap中。这种情况下,我们应该重写Dog类的equals()方法和hashCode()方法。
1 class Dog {
2 String color;
3
4 Dog(String color) {
5 this.color = color;
6 }
7
8 @Override
9 public boolean equals(Object o) {
10 return ((Dog) o).color.equals(this.color);
11 }
12
13 @Override
14 public int hashCode() {
15 return color.length();
16 }
17
18 public String toString() {
19 return color + " dog";
20 }
21 }
这样修改以后,我们的输出结果将变成如下所示:
1 3
2 red dog - 10
3 white dog - 20
4 black dog - 15
这种现象的背后魔法其实很简单。HashMap是不允许存储相同的元素的,但是,HashMap判断元素是否相同的依据就是该元素的键值是否相同。此处我们的键值类型是Dog,而两个Dog对象是否相同,其实是取决于Dog对象的equals()方法和hashCode()方法。对于自定义的类型而言,其默认的equals()方法和hashCode()方法来源与Java中的超级父类java.lang.Object,它们都是只在两个对象引用的是同一个对象时,才返回true值。但是,对于Dog对象,我们希望当它们的color一样时,就认为两个Dog是相等的,所以,我们必须重写其equals()方法和hashCode()方法。
三、TreeMap的元素顺序
TreeMap可以排序元素,问题是,TreeMap排序元素的依据是什么呢?对于Dog类,假设我们有如下的代码片段:
1 public class Main {
2 public static void main(String[] args) {
3 TreeMap<Dog, Integer> dogMap = new TreeMap<Dog, Integer>();
4 dogMap.put(new Dog("red"), 10);
5 dogMap.put(new Dog("black"), 15);
6 dogMap.put(new Dog("white"), 5);
7
8 for (Entry<Dog, Integer> entry : dogMap.entrySet()) {
9 System.out.println(entry.getKey() + " - " + entry.getValue());
10 }
11 }
12 }
此代码片段编译能够通过,但是运行时会出现如下所示的错误:
1 Exception in thread "main" java.lang.ClassCastException: collection.Dog cannot be cast to java.lang.Comparable
2 at java.util.TreeMap.put(Unknown Source)
3 at collection.Main.main(Main.java:35)
出现此错误的原因在于:TreeMap的底层实现是红黑树,并因此能够根据键值来排序元素。所以,TreeMap的键值必须可以相互比较大小,换言之,键值的类型必须实现java.util.Comparable接口。所以,我们应该修改Dog类成为如下所示:
1 class Dog implements Comparable {
2 String color;
3 int size;
4
5 Dog(String color, int size) {
6 this.color = color;
7 this.size = size;
8 }
9
10 @Override
11 public int compareTo(Dog o) {
12 return o.size - this.size;
13 }
14 }
15
16 public class Main {
17 public static void main(String[] args) {
18 TreeMap<Dog, Integer> dogMap = new TreeMap<Dog, Integer>();
19 dogMap.put(new Dog("red", 20), 5);
20 dogMap.put(new Dog("black", 30), 10);
21 dogMap.put(new Dog("white", 10), 15);
22 dogMap.put(new Dog("white", 10), 20);
23 for (Entry<Dog, Integer> entry : dogMap.entrySet()) {
24 System.out.println(entry.getKey().color + " - " + entry.getKey().size + " - " + entry.getValue());
25 }
26 }
27 }
该代码的输出结果是:
1 white - 10 - 20
2 red - 20 - 5
3 black - 30 - 10
注意:我们往dogMap中添加了四个Dog对象,输出结果却显示dogMap只包含三个Dog对象。
结论:TreeMap由于底层实现是红黑树,而HashMap、Hashtable和LinkedHashMap的底层数据结构都是哈希表,所以TreeMap集合要求其键值必须实现Comparable接口,并且使用其作为元素判等和比较大小的唯一依据。而其他三个Map都使用hashCode()方法和equals()方法来判断元素是否相等。
四、HashMap与Hashtable的区别
|
No
|
HashMap
|
HashTable
|
|
1
|
继承的是AbstractMap类
|
继承的的是Dicionary类
|
|
2
|
非线程安全
|
线程安全
|
|
3
|
允许存在null的key
|
不允许存在空key
|


2、线程安全
1)HashMap是非线程安全的(效率比较的高)
2)Hashtable是线程安全的(效率相对比较低)
3、key值是否可以存在null
1)HashMap可以允许为空

大家可以发现如果是空的key ,先判断一下HashMap的第一个Bucket,也就是第一个Entry<K,V>(HashMap和Hashtable中,都维护的是一个Entry<K,V>[] 链表数组)中是否存在,如果存在就修改值,如果不存在,就添加这个值(添加的位置是第一个 Bucket里面)。

2)Hashtable是不允许存在空的key

五、LinkedHashMap
LinkedHashMap基于链表的数据结构,是HashMap的子类,所以通过继承机制,拥有了HashMap的所有特性。而且,它还增加了保持元素插入顺序的特性(按照插入顺序排序元素)。

六、ConcurrentHashMap
这个是无锁多线程编程所提供的一个集合组合,是基于cpu层面的CAS原子操作,用到这个操作,只需要在取队列元素和添加队列元素的时候利用CAS原子操作,就可以保证多个线程对队列元素的有序存取。

浙公网安备 33010602011771号