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
  1、继承的父类
    1)HashMap继承的是AbstractMap类 
    2)Hashtable继承的的是Dicionary类 

     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原子操作,就可以保证多个线程对队列元素的有序存取。

 

 

posted @ 2020-11-12 17:21  阿森2020  阅读(399)  评论(0)    收藏  举报