map是最重要的数据结构之一。在这篇文章中,我会向你展示如:HashMap, TreeMap, HashTable 和 LinkedHashMap的使用以及它们的不同。

1. Map概述

map uml

HashMap, TreeMap, Hashtable 和 LinkedHashMap在Java SE中通常有4个相同实现点。简单一句话描述如下:

  • HashMap 是哈希列表的一个实现,它并没有针对key或者值进行排序。
  • TreeMap 是基于红黑树的一个实现,它针对key进行了排序。
  • LinkedHashMap 保留了插入顺序。
  • Hashtable 是同步的, 功能同HashMap。

这就告诉我们如果是线程安全的,我们就应该使用HashMap,因为Hashtable同步是回增加开销的。

HashMap

如果HashMap 的key是自定义对象,遵循规范应该重写 equals() 和 hashCode() 方法。看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Dog {
String color;
Dog(String c) {
color = c;
}
public String toString(){
return color + " dog";
}
}
public class TestHashMap {
public static void main(String[] args) {
HashMap<Dog, Integer> hashMap = new HashMap<Dog, Integer>();
Dog d1 = new Dog("red");
Dog d2 = new Dog("black");
Dog d3 = new Dog("white");
Dog d4 = new Dog("white");
hashMap.put(d1, 10);
hashMap.put(d2, 15);
hashMap.put(d3, 5);
hashMap.put(d4, 20);
//输出大小
System.out.println(hashMap.size());
//迭代HashMap
for (Entry<Dog, Integer> entry : hashMap.entrySet()) {
System.out.println(entry.getKey().toString() + " - " + entry.getValue());
}
}
}

输出:

4
white dog - 5
black dog - 15
red dog - 10
white dog - 20

注意,我们错误的添加了”white dogs” 2次,但是HashMap 同样保存了它。这是没有意义的,因为现在我们都搞不清楚了,白色的狗到底有多少是真的存在的。

正确的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Dog {
String color;
Dog(String c) {
color = c;
}
public boolean equals(Object o) {
return ((Dog) o).color == this.color;
}
public int hashCode() {
return color.length();
}
public String toString(){
return color + " dog";
}
}

现在的结果如下:

3
red dog - 10
white dog - 20
black dog - 15

其原因是HashMap不允许两个相同主键的元素。默认情况下,hashCode()和equals()方法在Object类中实现使用。默认的hashCode()方法用不同的整数来区别对象,这时只有当两个对象指向同一个引用,equals() 才会返回true。如果你不是很了解,请了解hashCode() 和 equals()

了解常用的HashMap方法,例如:迭代,打印等等。

3. TreeMap

TreeMap是通过key来排序的。让我们先来看看下面的例子就明白了“根据key来排序”的想法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Dog {
String color;
Dog(String c) {
color = c;
}
public boolean equals(Object o) {
return ((Dog) o).color == this.color;
}
public int hashCode() {
return color.length();
}
public String toString(){
return color + " dog";
}
}
public class TestTreeMap {
public static void main(String[] args) {
Dog d1 = new Dog("red");
Dog d2 = new Dog("black");
Dog d3 = new Dog("white");
Dog d4 = new Dog("white");
TreeMap<Dog, Integer> treeMap = new TreeMap<Dog, Integer>();
treeMap.put(d1, 10);
treeMap.put(d2, 15);
treeMap.put(d3, 5);
treeMap.put(d4, 20);
for (Entry<Dog, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + " - " + entry.getValue());
}
}
}

输出:

Exception in thread "main" java.lang.ClassCastException: collection.Dog cannot be cast to java.lang.Comparable
at java.util.TreeMap.put(Unknown Source)
at collection.TestHashMap.main(TestHashMap.java:35)

因为TreeMaps 根据 key值来排序,作为key的对象相互之间必须是可比较的,这就是为什么要实现 Comparable接口。比如你用String作为key,String是实现了 Comparable接口的。

好,没我们改变Dog类,实现 Comparable接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Dog implements Comparable<Dog>{
String color;
int size;
Dog(String c, int s) {
color = c;
size = s;
}
public String toString(){
return color + " dog";
}
@Override
public int compareTo(Dog o) {
return o.size - this.size;
}
}
public class TestTreeMap {
public static void main(String[] args) {
Dog d1 = new Dog("red", 30);
Dog d2 = new Dog("black", 20);
Dog d3 = new Dog("white", 10);
Dog d4 = new Dog("white", 10);
TreeMap<Dog, Integer> treeMap = new TreeMap<Dog, Integer>();
treeMap.put(d1, 10);
treeMap.put(d2, 15);
treeMap.put(d3, 5);
treeMap.put(d4, 20);
for (Entry<Dog, Integer> entry : treeMap.entrySet()) {
System.out.println(entry.getKey() + " - " + entry.getValue());
}
}
}

输出:

red dog - 10
black dog - 15
white dog - 20

它由key来排序,这里就是根据dog的数量来排序。

如果使用 “Dog d4 = new Dog(“white”, 10);” 替换 “Dog d4 = new Dog(“white”, 40);”,结果如下:

white dog - 20
red dog - 10
black dog - 15
white dog - 5

其原因是,TreeMap中使用compareTo()方法对键进行比较。不同大小被识别为不同的dog!

4. Hashtable

在 Java Doc写到:
HashMap大致相当于Hashtable,不同之处在于HashMap是不同步的,并且允许null值。

5. LinkedHashMap

LinkedHashMap 是 HashMap 的一个子类。这就意味着LinkedHashMap 继承了 HashMap 的所有功能。额外的链表插入是有序的。

我们把上面的HashMap的例子改成LinkedHashMap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Dog {
String color;
Dog(String c) {
color = c;
}
public boolean equals(Object o) {
return ((Dog) o).color == this.color;
}
public int hashCode() {
return color.length();
}
public String toString(){
return color + " dog";
}
}
public class TestHashMap {
public static void main(String[] args) {
Dog d1 = new Dog("red");
Dog d2 = new Dog("black");
Dog d3 = new Dog("white");
Dog d4 = new Dog("white");
LinkedHashMap<Dog, Integer> linkedHashMap = new LinkedHashMap<Dog, Integer>();
linkedHashMap.put(d1, 10);
linkedHashMap.put(d2, 15);
linkedHashMap.put(d3, 5);
linkedHashMap.put(d4, 20);
for (Entry<Dog, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + " - " + entry.getValue());
}
}
}

输出:

red dog - 10
black dog - 15
white dog - 20

HashMap和LinkedHashMap 所不同的是,插入顺序不保留。输出如下:

red dog - 10
white dog - 20
black dog - 15

了解一下ArrayList vs. LinkedList vs. Vector

参考文档