關於Java的散列桶, 以及附上一個案例-重寫map集合


為速度而散列:

SlowMap.java說明了創建一個新的Map並不困難。但正如它的名稱SlowMap所示,它不會很快,如果有更好的選擇就應該放棄它。它的問題在於對鍵的查詢,鍵沒有按照任何特定的順序保存,所以只能使用簡單的線性查詢,而線性查詢是最慢的查詢方式。

散列的價值在於速度

散列使得查詢得以快速進行。由於瓶頸在於鍵的查詢速度,因此解決方案之一就是保持鍵的排序狀態,然后使用Collections.binarySearch()進行查詢。

散列則更進一步,它將鍵保存在某處,以便能夠很快的找到。存儲一組元素的最快數據結構是數組,所以使用它來表示鍵的信息(請小心留意,我說的是鍵的信息,而不是鍵本身)。但是因為數組不能調整容量,因此就有了一個問題:我們希望在Map中保存的數量是不確定的值,但是如果鍵的數量被數組的容量限制了,該怎么辦呢?

答案就是:數組並不保存鍵本身。而是通過鍵對象生成一個數字,將其作為數組的下標。這個數字就是散列碼,由定義在Object中的、且可能由你的類覆蓋的hashCode()方法(計算機科學術語稱為散列函數)生成。

為了解決數組被固定的問題,不同的鍵可能產生相同的下標。也就是說,可能會有沖突。因此,數組多大就不重要了,任何鍵總能在數組中找到它的位置。

於是查詢一個值的過程首先就是計算散列碼然后使用散列碼查詢數組。如果能夠保證沒有沖突(如果值的數量是固定的,那么就有可能)那可能就是一個完美的散列函數,但是這種情況只是特例。通常,沖突由外部鏈接處理:數組並不直接保存值,而是保存值的list。然后對list中的值使用equals()方法進行線性的查詢。這部分的查詢自然會比較慢,但是,如果散列函數好的話,數組的每個位置就只有較少的值。因此,不是查詢整個list,而是快速的跳到素數的某個位置,只對很少的元素進行比較。這邊是HashMap快的原因。

理解了散列的原理,我們就能實現一個簡單的散列Map了:

 

?
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class MapEntry<k, v= "" > implements Map.Entry<k, v= "" > {
 
     private K key;
     private V value;
 
     public MapEntry(K key, V value) {
         this .key = key;
         this .value = value;
     }
 
     public K getKey() {
         return key;
     }
 
     public V getValue() {
         return value;
     }
 
     public V setValue(V value) {
         V result = this .value;
         this .value = value;
         return result;
     }
 
     @Override
     public int hashCode() {
         return (key == null ? 0 : key.hashCode())
                 ^ (value == null ? 0 : value.hashCode());
     }
 
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof MapEntry)) {
             return false ;
         }
         MapEntry me = (MapEntry) o;
         return (key == null ? me.getKey() == null : key.equals(me.getKey()))
                 && (value == null ? me.getValue() == null : value.equals(me
                         .getValue()));
     }
 
     @Override
     public String toString() {
         return key + " = " + value;
     }
}
 
class SimpleHashMap<k, v= "" > extends AbstractMap<k, v= "" > {
     static final int SIZE = 997 ;
 
     @SuppressWarnings ( "unchecked" )
     LinkedList<mapentry<k, v= "" >>[] buckets = new LinkedList[SIZE];
 
     public V put(K key, V value) {
         V oldValue = null ;
         int index = Math.abs(key.hashCode()) % SIZE;
 
         if (buckets[index] == null ) {
             buckets[index] = new LinkedList<mapentry<k, v= "" >>();
         }
         LinkedList<mapentry<k, v= "" >> bucket = buckets[index];
 
         MapEntry<k, v= "" > pair = new MapEntry<k, v= "" >(key, value);
         boolean found = false ;
         ListIterator<mapentry<k, v= "" >> it = bucket.listIterator();
         while (it.hasNext()) {
             MapEntry<k, v= "" > iPair = it.next();
             if (iPair.getKey().equals(key)) {
                 oldValue = iPair.getValue();
                 it.set(pair);
                 found = true ;
                 break ;
             }
         }
         if (!found) {
             buckets[index].add(pair);
         }
         return oldValue;
     }
     
     public V get(Object key) {
         int index = Math.abs(key.hashCode()) % SIZE;
         
         if (buckets[index] == null ) {
             return null ;
         }
         
         for (MapEntry<k, v= "" > iPair : buckets[index]) {
             if (iPair.getKey().equals(key)) {
                 return iPair.getValue();
             }
         }
         return null ;
     }
     
 
     @Override
     public Set<java.util.map.entry<k, v= "" >> entrySet() {
         Set<map.entry<k, v= "" >> set = new HashSet<map.entry<k,v>>();
         for (LinkedList<mapentry<k, v= "" >> bucket : buckets) {
             if (bucket == null ) {
                 continue ;
             }
             for (MapEntry<k, v= "" > mpair : bucket) {
                 set.add(mpair);
             }
         }
         return set;
     }
}
 
public class Main2 {
     public static void main(String[] args) {
         {CAPE VERDE=Praia, ANGOLA=Luanda, ETHIOPIA=Addis Ababa, BENIN=Porto-Novo, CONGO=Brazzaville, LESOTHO=Maseru, CENTRAL AFRICAN REPUBLIC=Bangui, EQUATORIAL GUINEA=Malabo, ERITREA=Asmara, COMOROS=Moroni, BURKINA FASO=Ouagadougou, GABON=Libreville, THE GAMBIA=Banjul, GUINEA=Conakry, EGYPT=Cairo, BURUNDI=Bujumbura, ALGERIA=Algiers, CAMEROON=Yaounde, GHANA=Accra, KENYA=Nairobi, COTE D 'IVOIR (IVORY COAST)=Yamoussoukro, BISSAU=Bissau, DJIBOUTI=Dijibouti, CHAD=N' djamena, BOTSWANA=Gaberone}
         [CAPE VERDE = Praia, ANGOLA = Luanda, ETHIOPIA = Addis Ababa, BENIN = Porto-Novo, CONGO = Brazzaville, LESOTHO = Maseru, CENTRAL AFRICAN REPUBLIC = Bangui, EQUATORIAL GUINEA = Malabo, ERITREA = Asmara, COMOROS = Moroni, BURKINA FASO = Ouagadougou, GABON = Libreville, THE GAMBIA = Banjul, GUINEA = Conakry, EGYPT = Cairo, BURUNDI = Bujumbura, ALGERIA = Algiers, CAMEROON = Yaounde, GHANA = Accra, KENYA = Nairobi, COTE D 'IVOIR (IVORY COAST) = Yamoussoukro, BISSAU = Bissau, DJIBOUTI = Dijibouti, CHAD = N' djamena, BOTSWANA = Gaberone]
         SimpleHashMap<string, string= "" > m = new SimpleHashMap<string, string= "" >();
         m.putAll(Countries.capitals( 25 ));
         System.out.println(m);
         System.out.println(m.entrySet());
     }
}</string,></string,></k,></mapentry<k,></map.entry<k,v></map.entry<k,></java.util.map.entry<k,></k,></k,></mapentry<k,></k,></k,></mapentry<k,></mapentry<k,></mapentry<k,></k,></k,></k,></k,>

由於散列表中的 “槽位”(slot)通常稱為 桶位(bucket),因此我們將表示實際散列表的數組命名為bucket。

 

為使散列分布均勻,桶的數量通常使用質數。注意,為了能夠自動處理沖突,使用了一個LinkedList的數組;

每一個新的元素只是直接添加到list末尾的某個特定的桶位中。即使Java不允許你創建泛型數組,那你也可以創建指向這種數組的引用。這里,向上轉型為這種數組是很方便的,這樣可以防止在后面的代碼中進行額外的轉型。

對於put方法,hashCode()將針對鍵而被調用,並且其結果被強制轉換為正數。為了是產生的數組適合bucket數組的大小,取摸操作符將按照該數組的尺寸取模。如果數組的某個位置是null,這表示還沒有元素被散列至此,所以,為了保存剛散列到該定位的對象需要創建愛你一個新的LinkedList。一般的過程是,查看當前位置的list中是否有相同的元素,如果有,則將舊的值付給oldValue,然后用新值取代舊值。標記found用來跟蹤是否找到舊的鍵值對,如果沒有,則將新的添加到list的末尾。

get()方法按照與put()方法相同的方式計算bucktes數組中的索引(這很重要,保證計算出相同的位置)如果此位置存在,則進行查詢。

注意,這個實現並不意味着對性能進行了調優;它只是想要展示散列映射表執行的各種操作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM