Java集合
Java集合框架:是一種工具類,就像是一個容器可以存儲任意數量的具有共同屬性的對象。
Java集合中成員很豐富,常用的集合有ArrayList,HashMap,HashSet等。線程安全的有Vector,HashTable。線程不安全的有LinkedList,TreeMap,ArrayList,HashMap等等。
集合中用到的數據結構有以下幾種:
數組:最常用的數據結構之一。數組的特點是長度固定,可以用下標索引,并且所有的元素的類型都是一致的。使用時盡量把數組封裝在一個類里,防止數據被錯誤的操作弄亂。
鏈表:是一種由多個節點組成的數據結構,并且每個節點包含有數據以及指向下一個節點的引用,在雙向鏈表里,還會有一個指向前一個節點的引用。例如,可以用單向鏈表和雙向鏈表來實現堆棧和隊列,因為鏈表的兩端都是可以進行插入和刪除的動作的。當然,也會有在鏈表的中間頻繁插入和刪除節點的場景。
樹:是一種由節點組成的數據結構,每個節點都包含數據元素,并且有一個或多個子節點,每個子節點指向一個父節點可以表示層級關系或者數據元素的順序關系。如果樹的每個子節點最多有兩個葉子節點,那么這種樹被稱為二叉樹。二叉樹是一種非常常用的樹形結構, 因為它的這種結構使得節點的插入和刪除都非常高效。樹的邊表示從一個節點到另外一個節點的快捷路徑。
堆棧:只允許對最后插入的元素進行操作(也就是后進先出,Last In First Out – LIFO)。如果你移除了棧頂的元素,那么你可以操作倒數第二個元素,依次類推。這種后進先出的方式是通過僅有的peek(),push()和pop()這幾個方法的強制性限制達到的。這種結構在很多場景下都非常實用,例如解析像(4+2)*3這樣的數學表達式,把源碼中的方法和異常按照他們出現的順序放到堆棧中,檢查你的代碼看看小括號和花括號是不是匹配的,等等。
隊列:和堆棧有些相似,不同之處在于在隊列里第一個插入的元素也是第一個被刪除的元素(即是先進先出)。這種先進先出的結構是通過只提供peek(),offer()和poll()這幾個方法來訪問數據進行限制來達到的。例如,排隊等待公交車,銀行或者超市里的等待列隊等等,都是可以用隊列來表示。
Java集合框架圖
[圖片上傳失敗...(image-4b8b54-1530872801038)]
Collection interface
如上圖所示,Collection接口是最基本的集合接口,它不提供直接的實現,Java SDK提供的類都是繼承自Collection的“子接口”如List,Set和Queue。Collection所代表的是一種規則,它所包含的元素都必須遵循一條或者多條規則。如有些允許出現重復元素而有些則不允許重復、有些必須要按照順序插入而有些則是散列,有些支持排序但是有些則不支持等等。
List
List接口是Collection接口下的子接口。List所代表的是有序的Collection,即它用某種特定的插入順序來維護元素順序。用戶可以對列表中每個元素的插入位置進行精確地控制,同時可以根據元素的整數索引(在列表中的位置)訪問元素,并搜索列表中的元素。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
ArrayList
ArrayList基于數組實現,可以通過下標索引直接查找到指定位置的元素,因此查找效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。它允許任何符合規則的元素插入甚至包括null。每一個ArrayList都有一個初始容量(10),該容量代表了數組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作(擴容1.5倍)。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。
ArrayList擅長于隨機訪問。同時ArrayList是非同步的,只能用在單線程環境下,多線程環境下可以考慮用Collections.synchronizedList(List l)函數返回一個線程安全的ArrayList類,也可以使用concurrent并發包下的CopyOnWriteArrayList類。
擴充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1個,也可能是一組)時,都要調用該方法來確保足夠的容量。當容量不足以容納當前的元素個數時,就設置新的容量為舊的容量的1.5倍,如果設置后的新容量還不夠,則直接新容量設置為傳入的參數(也就是所需的容量),而后用Arrays.copyof()方法將元素拷貝到新的數組。從中可以看出,當容量不夠時,每次增加元素,都要將原來的元素拷貝到一個新的數組中,非常之耗時,也因此建議在事先能確定元素數量的情況下,才使用ArrayList,否則建議使用LinkedList。
LinkedList
LinkedList同樣實現List接口,與ArrayList不同的是,LinkedList是基于雙向鏈表實現的,可以在任何位置進行高效地插入和移除操作。但是LinkedList不能隨機訪問,它所有的操作都是要按照雙重鏈表的需要執行。在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在List中進行插入和刪除操作。
與ArrayList一樣,LinkedList也是非同步的。如果多個線程同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在創建List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
Vector
與ArrayList相似,但是Vector是同步的。所以說Vector是線程安全的動態數組。它的操作與ArrayList幾乎一樣。
Stack
Stack繼承自Vector,實現一個后進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂的元素,empty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛創建后是空棧。
Set
Set接口繼承了Collection接口。Set集合中不能包含重復的元素,每個元素必須是唯一的。你只需將元素加入set中,重復的元素會自動移除。有三種常見的Set實現——HashSet, TreeSet和LinkedHashSet。如果你需要一個訪問快速的Set,你應該使用HashSet;當你需要一個排序的Set,你應該使用TreeSet;當你需要記錄下插入時的順序時,你應該使用LinedHashSet。
HashSet
HashSet是是基于 HashMap 實現的,底層采用 HashMap 來保存元素,所以它不保證set 的迭代順序;特別是它不保證該順序恒久不變。add()、remove()以及contains()等方法都是復雜度為O(1)的方法。由于HashMap中key不可重復,所以HashSet元素不可重復。可以存儲null元素,是線程不安全的。
TreeSet
TreeSet是一個有序集,基于TreeMap實現,是線程不安全的。
TreeSet底層采用TreeMap存儲,構造器啟動時新建TreeMap。TreeSet存儲元素實際為TreeMap存儲的鍵值對為<key,PRESENT>的key;,PRESENT為固定對象:private static final Object PRESENT = new Object().
TreeSet支持兩種兩種排序方式,通過不同構造器調用實現
自然排序:
public TreeSet() {
// 新建TreeMap,自然排序
this(new TreeMap<E,Object>());
}
Comparator排序:
public TreeSet(Comparator<? super E> comparator) {
// 新建TreeMap,傳入自定義比較器comparator
this(new TreeMap<>(comparator));
}
TreeSet支持正向/反向迭代器遍歷和foreach遍歷
// 順序TreeSet:迭代器實現
Iterator iter = set.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
// 順序遍歷TreeSet:foreach實現
for (Integer i : set) {
System.out.println(i);
}
// 逆序遍歷TreeSet:反向迭代器實現
Iterator iter1 = set.descendingIterator();
while (iter1.hasNext()) {
System.out.println(iter1.next());
}
LinkedHashSet
LinkedHashSet介于HashSet和TreeSet之間。哈希表和鏈接列表實現。基本方法的復雜度為O(1)。
LinkedHashSet 是 Set 的一個具體實現,其維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可為插入順序或是訪問順序。
LinkedHashSet 繼承于 HashSet,并且其內部是通過 LinkedHashMap 來實現的。有點類似于我們之前說的LinkedHashMap 其內部是基于 Hashmap 實現的一樣。
如果我們需要迭代的順序為插入順序或者訪問順序,那么 LinkedHashSet 是需要你首先考慮的。
LinkedHashSet 底層使用 LinkedHashMap 來保存所有元素,因為繼承于 HashSet,所有的方法操作上又與 HashSet 相同,因此 LinkedHashSet 的實現上非常簡單,只提供了四個構造方法,并通過傳遞一個標識參數,調用父類的構造器,底層構造一個 LinkedHashMap 來實現,在相關操作上與父類 HashSet 的操作相同,直接調用父類 HashSet 的方法即可。
package java.util;
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
private static final long serialVersionUID = -2851667679971038690L;
/**
* 構造一個帶有指定初始容量和加載因子的空鏈表哈希set。
*
* 底層會調用父類的構造方法,構造一個有指定初始容量和加載因子的LinkedHashMap實例。
* @param initialCapacity 初始容量。
* @param loadFactor 加載因子。
*/
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
/**
* 構造一個指定初始容量和默認加載因子0.75的新鏈表哈希set。
*
* 底層會調用父類的構造方法,構造一個指定初始容量和默認加載因子0.75的LinkedHashMap實例。
* @param initialCapacity 初始容量。
*/
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
/**
* 構造一個默認初始容量16和加載因子0.75的新鏈表哈希set。
*
* 底層會調用父類的構造方法,構造一個默認初始容量16和加載因子0.75的LinkedHashMap實例。
*/
public LinkedHashSet() {
super(16, .75f, true);
}
/**
* 構造一個與指定collection中的元素相同的新鏈表哈希set。
*
* 底層會調用父類的構造方法,構造一個足以包含指定collection
* 中所有元素的初始容量和加載因子為0.75的LinkedHashMap實例。
* @param c 其中的元素將存放在此set中的collection。
*/
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT | Spliterator.ORDERED);
}
}
通過觀察HashMap的源碼我們可以發現:
Hash Map的前三個構造函數,即訪問權限為public類型的構造函數均是以HashMap作為實現。而以LinkedHashMap作為實現的構造函數的訪問權限是默認訪問權限,即包內訪問權限。
即:在java編程中,通過new創建的HashSet對象均是以HashMap作為實現基礎。只有在jdk中java.util包內的源代碼才可能創建以LinkedHashMap作為實現的HashSet(LinkedHashSet就是通過封裝一個以LinkedHashMap為實現的HashSet來實現的)。
只有包含三個參數的構造函數才是采用的LinkedHashMap作為實現。
Map
Map與List、Set接口不同,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關系。也就是說一個key對應一個value,所以它不能存在相同的key值,當然value值可以相同。key可以為空,但是只允許出現一個null。它的主要實現類有HashMap、HashTable、LinkedHashMap、TreeMap。
HashMap
HashMap 是 Map 的一個實現類,它代表的是一種鍵值對的數據存儲形式。
大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。
HashMap最多只允許一條記錄的鍵為null,允許多條記錄的值為null。遇到key為null的時候,調用putForNullKey方法進行處理,而對value沒有處理。不保證有序(比如插入的順序)、也不保證序不隨時間變化。
jdk 8 之前,其內部是由數組+鏈表來實現的,而 jdk 8 對于鏈表長度超過 8 的鏈表將轉儲為紅黑樹。
HashMap非線程安全,即任一時刻可以有多個線程同時寫HashMap,可能會導致數據的不一致。如果需要滿足線程安全,可以用 Collections的synchronizedMap方法使HashMap具有線程安全的能力,或者使用ConcurrentHashMap。
hash數組的默認大小是16,而且大小一定是2的指數
HashTable
Hashtable和HashMap一樣也是散列表,存儲元素也是鍵值對,底層實現是一個Entry數組+鏈表。Hashtable繼承于Dictionary類(Dictionary類聲明了操作鍵值對的接口方法),實現Map接口(定義鍵值對接口)。HashTable是線程安全的,它的大部分類都被synchronized關鍵字修飾。key和value都不可為null。
hash數組默認大小是11,擴充方式是old*2+1
LinkedHashMap
LinkedHashMap繼承自HashMap實現了Map接口。基本實現同HashMap一樣(底層基于數組+鏈表+紅黑樹實現),不同之處在于LinkedHashMap保證了迭代的有序性。其內部維護了一個雙向鏈表,解決了 HashMap不能隨時保持遍歷順序和插入順序一致的問題。
除此之外,LinkedHashMap對訪問順序也提供了相關支持。在一些場景下,該特性很有用,比如緩存。
在實現上,LinkedHashMap很多方法直接繼承自HashMap,僅為維護雙向鏈表覆寫了部分方法。
默認情況下,LinkedHashMap的迭代順序是按照插入節點的順序。也可以通過改變accessOrder參數的值,使得其遍歷順序按照訪問順序輸出。
TreeMap
TreeMap繼承自AbstractMap抽象類,并實現了SortedMap接口,如下圖所示:
[圖片上傳失敗...(image-fd7a40-1530872801038)]
TreeMap集合是基于紅黑樹(Red-Black tree)的 NavigableMap實現。該集合最重要的特點就是可排序,該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決于使用的構造方法。
關于集合的常見問題
List和Map的區別
都是Java常用的容器,都是接口。不同的是List存儲的是單列的集合,Map存儲的是key-value鍵值對的集合。List中允許出現重復元素,Map中不允許key重復。List集合是有序的(儲存有序),Map集合是無序的(存儲無序)
Set中的元素不能重復,如何實現?
Set大多都用的Map接口的實現類來實現的(HashSet基于HashMap實現,TreeSet基于TreeMap實現,LinkedHashSet基于LinkedHashMap實現)
在HashMap中通過如下實現來保證key值唯一
// 1. 如果key 相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 2. 修改對應的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
添加元素的時候,如果key(也對應的Set集合的元素)相等,那么則修改value值。而在Set集合中,value值僅僅是一個Object對象罷了(該對象對Set本身而言是無用的)。
也就是說:Set集合如果添加的元素相同時,是根本沒有插入的(僅修改了一個無用的value值)。從源碼(HashMap)中也看出來,==和equals()方法都有使用!
Vector和ArrayList
相同點:
這兩個類都實現了List接口,他們都是有序的集合(儲存有序),底層都用數組實現。可以通過索引來獲取某個元素。允許元素重復和出現null值。ArrayList和Vector的迭代器實現都是fail-fast的。
不同點:
vector是線程同步的,所以它也是線程安全的,而arraylist是線程異步的,是不安全的。如果不考慮到線程的安全因素,一般用arraylist效率比較高。
擴容時,arraylist擴容1.5倍,vector擴容2倍(或者擴容指定的大小)
ArrayList 和Vector是采用數組方式存儲數據,此數組元素數大于實際存儲的數據以便增加和插入元素,都允許直接序號索引元素,但是插入數據要設計到數組元素移動等內存操作,所以索引數據快插入數據慢,Vector由于使用了synchronized方法(線程安全)所以性能上比ArrayList要差,LinkedList使用雙向鏈表實現存儲,按序號索引數據需要進行向前或向后遍歷,但是插入數據時只需要記錄本項的前后項即可,所以插入數度較快!
Aarraylist和Linkedlist
ArrayList是基于數組實現的,LinkedList基于雙向鏈表實現的。
ArrayList它支持以下標位置進行索引出對應的元素(隨機訪問),而LinkedList則需要遍歷整個鏈表來獲取對應的元素。因此一般來說ArrayList的訪問速度是要比LinkedList要快的
ArrayList由于是數組,對于刪除和修改而言消耗是比較大(復制和移動數組實現),LinkedList是雙向鏈表刪除和修改只需要修改對應的指針即可,消耗是很小的。因此一般來說LinkedList的增刪速度是要比ArrayList要快的
LinkedList比ArrayList消耗更多的內存,因為LinkedList中的每個節點存儲了前后節點的引用。
對于增加/刪除元素操作
如果增刪都是在末尾來操作(每次調用的都是remove()和add()),此時ArrayList就不需要移動和復制數組來進行操作了。如果數據量有百萬級的時,速度是會比LinkedList要快的。
如果刪除操作的位置是在中間。由于LinkedList的消耗主要是在遍歷上,ArrayList的消耗主要是在移動和復制上(底層調用的是arraycopy()方法,是native方法)。
LinkedList的遍歷速度是要慢于ArrayList的復制移動速度的
如果數據量有百萬級的時,還是ArrayList要快。
哪些集合類提供對元素的隨機訪問?
ArrayList、HashMap、TreeMap和HashTable類提供對元素的隨機訪問。
Enumeration和Iterator接口的區別
Enumeration的速度是Iterator的兩倍,也使用更少的內存。Enumeration是非常基礎的,也滿足了基礎的需要。但是,與Enumeration相比,Iterator更加安全,因為當一個集合正在被遍歷的時候,它會阻止其它線程去修改集合。
Iterator的方法名比Enumeration更科學
Iterator有fail-fast機制,比Enumeration更安全
Iterator能夠刪除元素,Enumeration并不能刪除元素
Iterater和ListIterator之間有什么區別?
我們可以使用Iterator來遍歷Set和List集合,而ListIterator只能遍歷List。
Iterator只可以向前遍歷,而LIstIterator可以雙向遍歷。
ListIterator從Iterator接口繼承,然后添加了一些額外的功能,比如添加一個元素、替換一個元素、獲取前面或后面元素的索引位置。
Java中HashMap的key值要是為類對象則該類需要滿足什么條件?
需要同時重寫該類的hashCode()方法和它的equals()方法。
從源碼可以得知,在插入元素的時候是先算出該對象的hashCode。如果hashcode相等話的。那么表明該對象是存儲在同一個位置上的。
如果調用equals()方法,兩個key相同,則替換元素
如果調用equals()方法,兩個key不相同,則說明該hashCode僅僅是碰巧相同,此時是散列沖突,將新增的元素放在桶子上
重寫了equals()方法,就要重寫hashCode()的方法。因為equals()認定了這兩個對象相同,而同一個對象調用hashCode()方法時,是應該返回相同的值的!
HashSet與HashMap
HashSet 實現了 Set 接口,它不允許集合中有重復的值,當我們提到 HashSet 時,第一件事情就是在將對象存儲在 HashSet 之前,要先確保對象重寫 equals()和 hashCode()方法,這樣才能比較對象的值是否相等,以確保set中沒有儲存相等的對象。如果我們沒有重寫這兩個方法,將會使用這個方法的默認實現。
public boolean add(Object o)方法用來在 Set 中添加元素,當元素值重復時則會立即返回 false,如果成功添加的話會返回 true。
HashMap 實現了 Map 接口,Map 接口對鍵值對進行映射。Map 中不允許重復的鍵。Map 接口有兩個基本的實現,HashMap 和 TreeMap。TreeMap 保存了對象的排列次序,而 HashMap 則不能。HashMap 允許鍵和值為 null。HashMap 是非 synchronized 的,但 collection 框架提供方法能保證 HashMap synchronized,這樣多個線程同時訪問 HashMap 時,能保證只有一個線程更改 Map。
public Object put(Object Key,Object value)方法用來將元素添加到 map 中。
HashMap | HashSet |
---|---|
HashMap實現了Map接口 | HashSet實現了Set接口 |
HashMap儲存鍵值對 | HashSet僅僅存儲對象 |
使用put()方法將元素放入map中 | 使用add()方法將元素放入set中 |
HashMap中使用鍵對象來計算hashcode值 | HashSet使用成員對象來計算hashcode值,對于兩個對象來說hashcode可能相同,所以equals()方法用來判斷對象的相等性,如果兩個對象不同的話,那么返回false |
hashtable與hashmap
相同點:
儲存結構和實現基本相同,都是是實現的Map接口
不同點:
HashTable是同步的,HashMap是非同步的,需要同步的時候可以ConcurrentHashMap方法
HashMap允許為null,HashTable不允許為null
繼承不同,HashMap繼承的是AbstractMap,HashTable繼承的是Dictionary
HashMap提供對key的Set進行遍歷,因此它是fail-fast的,但HashTable提供對key的Enumeration進行遍歷,它不支持fail-fast。
HashTable是一個遺留類,如果需要保證線程安全推薦使用CocurrentHashMap
HashMap與TreeMap
HashMap通過hashcode對其內容進行快速查找,而TreeMap中所有的元素都保持著某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。HashMap中元素的排列順序是不固定的)。
在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會更好。使用HashMap要求添加的鍵類明確定義了hashCode()和 equals()的實現。 這個TreeMap沒有調優選項,因為該樹總處于平衡狀態。
集合框架中的泛型有什么優點?
Java1.5引入了泛型,所有的集合接口和實現都大量地使用它。泛型允許我們為集合提供一個可以容納的對象類型,因此,如果你添加其它類型的任何元素,它會在編譯時報錯。這避免了在運行時出現ClassCastException,因為你將會在編譯時得到報錯信息。泛型也使得代碼整潔,我們不需要使用顯式轉換和instanceOf操作符。它也給運行時帶來好處,因為不會產生類型檢查的字節碼指令。
comparable 和 comparator的不同之處?
comparable接口實際上是出自java.lang包
它有一個 compareTo(Object obj)方法來將objects排序
comparator接口實際上是出自 java.util 包
它有一個compare(Object obj1, Object obj2)方法來將objects排序
如何保證一個集合線程安全?
Vector, Hashtable, Properties 和 Stack 都是同步的類,所以它們都線程安全的,可以被使用在多線程環境中
使用Collections.synchronizedList(list)) 方法,可以保證list類是線程安全的
使用java.util.Collections.synchronizedSet()方法可以保證set類是線程安全的。
TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。
TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口從而根據鍵對元素進行排序。
Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,但是要求傳入第二個參數,參數是Comparator接口的子類型(需要重寫compare方法實現元素的比較),相當于一個臨時定義的排序規則,其實就是通過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。
什么是Java優先級隊列?
Java PriorityQueue是一個數據結構,它是Java集合框架的一部分。 它是一個隊列的實現,其中元素的順序將根據每個元素的優先級來決定。 實例化PriorityQueue時,可以在構造函數中提供比較器。 該比較器將決定PriorityQueue集合實例中元素的排序順序。
Java hashCode()和equals()方法。
equals()方法用于確定兩個Java對象的相等性。 當我們有一個自定義類時,我們需要重寫equals()方法并提供一個實現,以便它可以用來找到它的兩個實例之間的相等性。 通過Java規范,equals()和hashCode()之間有一個契約。 它說,“如果兩個對象相等,即obj1.equals(obj2)為true,那么obj1.hashCode()和obj2.hashCode()必須返回相同的整數”
無論何時我們選擇重寫equals(),我們都必須重寫hashCode()方法。 hashCode()用于計算位置存儲區和key。