原文 HashMap vs. TreeMap vs. Hashtable vs. LinkedHashMap
Map是最重要的数据结构之一,下面来看下怎么使用他们。
1,Map综述
在java SE中通常有上述4种Map,若要用一句话来描述他们的话:
HashMap 是用Hash Table实现的,在key或value上无序
TreeMap是由red-black tree实现的,在key上有序
LinkedHashMap 保留了插入顺序
HashTable是同步的,相较于HashMap
2,HashMap
如果HashMap的key是自定义的,那就应该重写equals()和hashCode()方法。
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);
//print size
System.out.println(hashMap.size());
//loop 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
注意,我们错误的添加了2个“white dog”,但HashMap都接受了,但这是没有意义的,因为我们不知道到底添加了几个“white dog”。
Dog类应该这样写:
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
3,TreeMap
TreeMap是按key排序的。
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)
因为TreeMap是按key来排序的,所以key必须可以能够相互比较,这时key的类型必须实现Comparable接口的原因。
重新定义Dog
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());
}
}
}
或者在初始化TreeMap时赋值一个Comparator对象,TreeMap(Comparator<? super K> comparator) 。不论是TreeMap还是TreeSet,涉及排序,要么集合中对象实现了Comparable接口,要么有Comparator对象。
输出:
red dog - 10
black dog - 15
white dog - 20
如果把“ 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去比较key,不同的size表示不同的dog!(这个解释是错误的,TreeMap确实是用CompareTo去去比较key,但是否是同一个Dog,却不是由compareTo判断的,判断一个类型的两个对象是否相同是由equals方法决定的,这里d3,d4分别表示不同的Dog,因为equals没有被重写,走默认的,这里d3,d4 size相同时之所以保留一个是因为 TreeMap 在排序时用 size 比较,当 size 相同时,后面的替换前面的。证伪:treeMap.put 时,hashMap.put(d3, 5); 在 hashMap.put(d4, 20); 之后,运行结果保留 d3,;把 TreeMap 换成 HashMap ,put dog1-4,打印大小是 4,HashMap 也是要求 key 唯一的 。 2017.2.7)
4,HashTable
来着java doc:
HashMap基本上等同于HashTable,除了不是同步的和允许null (HashMap允许多个value可以是null,只允许有一个key可以为null)。
5,LinkedHashMap
LinkedHashMap是HashMap的子类,这意味着它继承了HashMap的特性,另外,它还可以保留插入的顺序。
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
如果上例中的LinkedHashMap换成HashMap,那输出会变成:
red dog - 10
white dog - 20
black dog - 15
------------update 2015.3.22---------------
所有实现Map接口的类,key都对应唯一的Value,属于一一对应,如果有重复的key,那之前key对应的value值将会被覆盖掉。
TreeMap通过Key对象自身实现了Comparable接口,或者在其构造方法中传入实现了Comparator的对象,然后根据compareTo()或者compare()方法返回值来确定key的唯一性和排序。
------------2015.5.10----------
理解Map.Entry
在接口Map中定义了内部接口Entry,所谓Map.Entry其实就是key-value pair,获得Map.Entry引用的唯一方法是Map.entrySet(),Map.keySet()获得的是key的Set集合。
<span style="font-size:18px;">public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
Book book = demo.new Book();
book.name = "b1";
Book book2 = demo.new Book();
book2.name = "b2";
Map<Book,Integer> map = new HashMap<>();
map.put(book2, 2);
map.put(book, 1);
//keySet()
Set<Book> keys = map.keySet();
Iterator<Book> it = keys.iterator();
while(it.hasNext()){
Book k = it.next();
System.out.println(k.name+"\t"+map.get(k));
}
//高级for循环中使用的实际上也是Iterator
for(Book b : keys){
System.out.println(b.name);
}
//entrySet()
Set<Map.Entry<Book, Integer>> meSet = map.entrySet();
//iterator
Iterator<Map.Entry<Book, Integer>> it2 = meSet.iterator();
while(it2.hasNext()){
Map.Entry<Book, Integer> me = it2.next();
System.out.println(me.getKey().name+"...."+me.getValue());
}
//for each
for(Map.Entry<Book, Integer> m : meSet){
System.out.println(m.getKey().name+"\t"+m.getValue());
}
}
public class Book {
public String name;
private List<String> titles;
public void setTitles(List<String> titles) {
this.titles = titles;
}
public List<String> getTitles() {
return titles;
}
@Override
public boolean equals(Object obj) {
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
Book b = (Book) obj;
return b.name.equals(name);
}
}
}</span>