java資料結構之Map
Java中的List和Map是使用頻率非常高的資料結構。這裡介紹下常用的Map實現。
先來看下類圖:
HashMap的實現原理
HashMap與HashTable的異同
HashTable與HashMap的儲存機制基本相同,都是採用陣列+連結串列。
不同點:
1.HashMap是非執行緒安全的,HashTable是執行緒安全的。它的大多數方法都加了synchronized。
2.HashMap允許key和value的值為null,HashTable不允許null值的存在。在HashTable的put方法中,如果V為null,直接丟擲NullPointerException。
3.因為HashTable加了同步處理,所以HashMap效率高於HashTable。
HashTable與ConcurrentHashMap有何不同?
HashTable與ConcurrentHashMap都是執行緒安全的Map,有何不同?
HashTable使用的synchronized對方法加鎖,實際鎖住的是整個物件;而ConcurrentHashMap使用的是lock,這樣在操作的時候鎖住的不是這個物件。
而且ConcurrentHashMap採用了分段鎖的設計,只有在同一個分段內才存在競態關係,不同的分段鎖之間沒有鎖競爭。相比於對整個Map加鎖的設計,分段鎖大大的提高了高併發環境下的處理能力。
ConcurrentHashMap使用多個子Hash表,即Segment。每個Segment是一個子Hash表。
ConcurrentHashMap要避免呼叫size()和containsValue()方法,會對整個Map進行掃描。
LinkedHashMap——有序的HashMap
LinkedHashMap繼承自HashMap,在HashMap的基礎上增加了一個連結串列,用以存放元素的順序。
LinkedHashMap提供2種類型的順序:一是元素插入時的順序;二是最近訪問的順序。
可以通過下面的建構函式指定排序行為:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
}
accessOrder為true時表示按照元素訪問時間排序;為false時表示按照元素插入的順序排序。預設是false。
示例程式碼:
Map<String,String> map = new LinkedHashMap<>(16,0.75f,false);
map.put("hello","hello");
map.put("world","world");
map.put("!","!");
for (Iterator<String> iterator = map.keySet().iterator();iterator.hasNext();) {
String key = iterator.next();
System.out.println(key + "->" + map.get(key));
}
輸出如下:
hello->hello
world->world
!->!
按照元素插入的順序輸出。
如果把上面的建構函式的最後一個引數accessOrder改為true,則在迭代器遍歷時會報錯。
Map<String,String> map = new LinkedHashMap<>(16,0.75f,true);
錯誤資訊:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:390)
at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:401)
at com.tommy.core.test.Test.main(Test.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
ConcurrentModificationException異常一般是在對集合迭代的過程中被修改時丟擲。不僅僅是LinkedHashMap,所有的集合都不允許在迭代器中修改集合的結構。
因為我們將accessOrder改為true,表示按照最後訪問的時間排序。在迭代器中遍歷時會將訪問的元素移動連結串列到尾部,發生了修改操作。
我們看下LinkedHashMap的get方法:
public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
可以看到,在get方法中呼叫了e.recordAccess()方法,recordAccess方法中如果accessOrder=true會有修改操作。所以在迭代器中迴圈get時出錯了。
但是,如果把遍歷的程式碼改一下,執行就正常了。
for (Iterator<Map.Entry<String,String>> iterator = map.entrySet().iterator();iterator.hasNext();) {
Map.Entry<String,String> entry = iterator.next();
System.out.println(entry.getKey() + "->" + entry.getValue());
}
沒報錯是因為沒有對資料結構修改,因為直接呼叫的getKey和getValue方法。
TreeMap——另一種排序的Map
TreeMap實現了SortedMap介面,這意味著它可以對元素進行排序。
但TreeMap與LinkedHashMap的排序不同,它是通過指定的Comparator或Comparable確定。
為了確定Key的排序演算法,可以通過2種方式制定:
1.在TreeMap的建構函式中注入一個Comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2.使用實現了Comparable介面的Key。
如果不指定Comparable或Compartor,則在put時會報ClassCastException。
Exception in thread "main" java.lang.ClassCastException: com.tommy.core.test.Test$Student cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1188)
at java.util.TreeMap.put(TreeMap.java:531)
at com.tommy.core.test.Test.main(Test.java:62)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
因為建構函式沒有傳入Comparator,TreeMap認為你的Key(即這裡的Student)應該實現了Comparable介面,所以會做一個轉換,就出錯了。
示例程式碼:
class Student implements Comparable<Student>{
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int compareTo(Student o) {
if (o == null || this.id > o.id
) {
return 1;
}
if (this.id == o.id) {
return 0;
}
return -1;
}
}
TreeMap<Student,Student> map = new TreeMap<>();
Student s1 = new Student(1003,"張三");
Student s2 = new Student(1002,"李四");
Student s3 = new Student(1001,"王五");
map.put(s1,s1);
map.put(s2,s2);
map.put(s3,s3);
for(Iterator<Student> iterator = map.keySet().iterator();iterator.hasNext();) {
Student student = iterator.next();
System.out.println(student.id+":"+student.name);
}
或者TreeMap的初始化指定Comparator:
TreeMap<Student, Student> map = new TreeMap<>(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
boolean f1 = s1 == null && s2 == null;
return (f1 || s1.id == s2.id) ? 0 : (s1.id > s2.id ? 1 : -1);
}
});
輸出:
1001:王五
1002:李四
1003:張三
此外,TreeMap還提供了一系列有用的方法,用於獲取大於,小於,以及2個Key直接的子Map的功能。
如果需要使用Map,並且需要實現排序的功能,建議使用TreeMap。