我就不寫(xiě)了,給個(gè)提示吧:
成都創(chuàng)新互聯(lián)于2013年創(chuàng)立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元科爾沁左翼做網(wǎng)站,已為上家服務(wù),為科爾沁左翼各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18982081108
建一個(gè)類,名字就叫員工,它有三個(gè)屬性,分別是你要的三個(gè)數(shù)據(jù),名字、工齡、工號(hào)。然后,每次put的時(shí)候這樣:put('1234',員工1);以員工工號(hào)為key,類員工為value
java數(shù)據(jù)結(jié)構(gòu)-HashMap
一直以來(lái)似乎都有一個(gè)錯(cuò)覺(jué),認(rèn)為map跟其他的集合類一樣繼承自Collection,其實(shí)不然,Map和Collection在結(jié)構(gòu)層次上是沒(méi)有任何關(guān)系的,通過(guò)查看源碼可以發(fā)現(xiàn)map所有操作都是基于key-value對(duì),而不是單獨(dú)的元素。
下面以HashMap為例子,深入對(duì)Map的實(shí)現(xiàn)機(jī)制進(jìn)行了解,在這個(gè)過(guò)程中,請(qǐng)打開(kāi)jdk源碼。
Hash算法
HashMap使用Hash算法,所以在解剖HashMap之間,需要先簡(jiǎn)單的了解Hash算法,Hash算法一般也成為散列算法,通過(guò)散列算法將任意的值轉(zhuǎn)化成固定的長(zhǎng)度輸出,該輸出就是散列值,這是一種壓縮映射,也就是,散列值的空間遠(yuǎn)遠(yuǎn)小于輸入的值空間。
簡(jiǎn)單的說(shuō),hash算法的意義在于提供了一種快速存取數(shù)據(jù)的方法,它用一種算法建立鍵值與真實(shí)值之間的對(duì)應(yīng)關(guān)系,(每一個(gè)真實(shí)值只能有一個(gè)鍵值,但是一個(gè)鍵值可以對(duì)應(yīng)多個(gè)真實(shí)值),這樣可以快速在數(shù)組等里面存取數(shù)據(jù)。
下面我們建立一個(gè)HashMap,然后往里面放入12對(duì)key-value,這個(gè)HashMap的默認(rèn)數(shù)組長(zhǎng)度為16,我們的key分別存放在該數(shù)組的格子中,每個(gè)格子下面存放的元素又是以鏈表的方式存放元素。
public static void main(String[] args) {
Map map = new HashMap();
map.put("What", "chenyz");
map.put("You", "chenyz");
map.put("Don't", "chenyz");
map.put("Know", "chenyz");
map.put("About", "chenyz");
map.put("Geo", "chenyz");
map.put("APIs", "chenyz");
map.put("Can't", "chenyz");
map.put("Hurt", "chenyz");
map.put("you", "chenyz");
map.put("google", "chenyz");
map.put("map", "chenyz");
map.put("hello", "chenyz");
}
當(dāng)我們新添加一個(gè)元素時(shí),首先我們通過(guò)Hash算法計(jì)算出這個(gè)元素的Hash值的hashcode,通過(guò)這個(gè)hashcode的值,我們就可以計(jì)算出這個(gè)新元素應(yīng)該存放在這個(gè)hash表的哪個(gè)格子里面,如果這個(gè)格子中已經(jīng)存在元素,那么就把新的元素加入到已經(jīng)存在格子元素的鏈表中。
運(yùn)行上面的程序,我們對(duì)HashMap源碼進(jìn)行一點(diǎn)修改,打印出每個(gè)key對(duì)象的hash值
What--hash值:8
You--hash值:3
Don't--hash值:7
Know--hash值:13
About--hash值:11
Geo--hash值:12
APIs--hash值:1
Can't--hash值:7
Hurt--hash值:1
you--hash值:10
google--hash值:3
map--hash值:8
hello--hash值:0
計(jì)算出來(lái)的Hash值分別代表該key應(yīng)該存放在Hash表中對(duì)應(yīng)數(shù)字的格子中,如果該格子已經(jīng)有元素存在,那么該key就以鏈表的方式依次放入格子中
從上表可以看出,Hash表是線性表和鏈表的綜合所得,根據(jù)數(shù)據(jù)結(jié)構(gòu)的定義,可以得出粗劣的結(jié)論,Hash算法的存取速度要比數(shù)組差一些,但是比起單純的鏈表,在查找和存取方面卻要好多。
如果要查找一個(gè)元素時(shí),同樣的方式,通過(guò)Hash函數(shù)計(jì)算出這個(gè)元素的Hash值hashcode,然后通過(guò)這個(gè)hashcode值,直接找到跟這個(gè)hash值相對(duì)應(yīng)的線性格子,進(jìn)如該格子后,對(duì)這個(gè)格子存放的鏈表元素逐個(gè)進(jìn)行比較,直到找到對(duì)應(yīng)的hash值。
在簡(jiǎn)單了解完Hash算法后,我們打開(kāi)HashMap源碼
初始化HashMap
下面我們看看Map map = new HashMap();這段代碼究竟做了什么,發(fā)生了什么數(shù)據(jù)結(jié)構(gòu)的變化。
HashMap中幾個(gè)重要的屬性
transient Entry[] table;
用來(lái)保存key-value的對(duì)象Entry數(shù)組,也就是Hash表
transient int size;
返回HashMap的鍵值對(duì)個(gè)數(shù)
final float loadFactor;
負(fù)載因子,用來(lái)決定Entry數(shù)組是否擴(kuò)容的因子,HashMap默認(rèn)是0.75f
int threshold;
重構(gòu)因子,(capacity * load factor)負(fù)載因子與Entry[]數(shù)組容積的乘值
public class HashMapK,V
extends AbstractMapK,V
implements MapK,V, Cloneable, Serializable
{
int threshold;
final float loadFactor;
transient Entry[] table;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int DEFAULT_INITIAL_CAPACITY = 16;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor = 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 = initialCapacity
int capacity = 1;
while (capacity initialCapacity)
capacity = 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
以public HashMap(int initialCapacity, float loadFactor)構(gòu)造函數(shù)為例,另外兩個(gè)構(gòu)造函數(shù)實(shí)際上也是以同種方式來(lái)構(gòu)建HashMap.
首先是要確定hashMap的初始化的長(zhǎng)度,這里使用的策略是循環(huán)查出一個(gè)大于initialCapacity的2的次方的數(shù),例如initialCapacity的值是10,那么大于10的數(shù)是2的4次方,也就是16,capacity的值被賦予了16,那么實(shí)際上table數(shù)組的長(zhǎng)度是16,之所以采用這樣的策略來(lái)構(gòu)建Hash表的長(zhǎng)度,是因?yàn)?的次方運(yùn)算對(duì)于計(jì)算機(jī)來(lái)說(shuō)是有相當(dāng)?shù)男省?/p>
loadFactor,被稱為負(fù)載因子,HashMap的默認(rèn)負(fù)載因子是0.75f
threshold,接下來(lái)是重構(gòu)因子,由負(fù)載因子和容量的乘機(jī)組成,它表示當(dāng)HashMap元素被存放了多少個(gè)之后,需要對(duì)HashMap進(jìn)行重構(gòu)。
通過(guò)這一系列的計(jì)算和定義后,初始化Entry[] table;
put(key,value)
接下來(lái)看一對(duì)key-value是如何被存放到HashMap中:put(key,value)
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
System.out.println(key+"--hash值:"+i);//這就是剛才程序打印出來(lái)的key對(duì)應(yīng)hash值
for (EntryK,V e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
static int hash(int h) {
h ^= (h 20) ^ (h 12);
return h ^ (h 7) ^ (h 4);
}
static int indexFor(int h, int length) {
return h (length-1);
}
這里是整個(gè)hash的關(guān)鍵,請(qǐng)打開(kāi)源碼查看一步一步查看。
hash(key.hashCode()) 計(jì)算出key的hash碼 //對(duì)于hash()的算法,這里有一篇分析很透徹的文章HashMap hash方法分析
indexFor(hash, table.length) 通過(guò)一個(gè)與算法計(jì)算出來(lái),該key應(yīng)在存放在Hash表的哪個(gè)格子中。
for (EntryK,V e = table[i]; e != null; e = e.next) 然后再遍歷table[i]格中的鏈表,判斷是否已經(jīng)存在一樣的key,如果存在一樣的key值,那么就用新的value覆蓋舊的value,并把舊的value值返回。
addEntry(hash, key, value, i) 如果經(jīng)過(guò)遍歷鏈表沒(méi)有發(fā)現(xiàn)同樣的key,那么進(jìn)行addEntry函數(shù)的操作,增加當(dāng)前key到hash表中的第i個(gè)格子中的鏈表中
void addEntry(int hash, K key, V value, int bucketIndex) {
EntryK,V e = table[bucketIndex];
table[bucketIndex] = new EntryK,V(hash, key, value, e);
if (size++ = threshold)
resize(2 * table.length);
}
EntryK,V e = table[bucketIndex]; 創(chuàng)建一個(gè)Entry對(duì)象來(lái)存放鍵值(ps:Entry對(duì)象是一個(gè)鏈表對(duì)象)
table[bucketIndex] = new EntryK,V(hash, key, value, e); 將Entry對(duì)象添加到鏈表中
if (size++ = threshold) resize(2 * table.length); 最后將size進(jìn)行自增,判斷size值是否大于重構(gòu)因子,如果大于那么就是用resize進(jìn)行擴(kuò)容重構(gòu)。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
這里為什么是否需要擴(kuò)容重構(gòu),其實(shí)是涉及到負(fù)載因子的性能問(wèn)題
loadFactor負(fù)載因子
上面說(shuō)過(guò)loadFactor是一個(gè)hashMap的決定性屬性,HashSet和HashMap的默認(rèn)負(fù)載因子都是0.75,它表示,如果哈希表的容量超過(guò)3/4時(shí),將自動(dòng)成倍的增加哈希表的容量,這個(gè)值是權(quán)衡了時(shí)間和空間的成本,如果負(fù)載因子較高,雖然會(huì)減少對(duì)內(nèi)存空間的需求,但也會(huì)增加查找數(shù)據(jù)的時(shí)間開(kāi)銷,無(wú)論是put()和get()都涉及到對(duì)數(shù)據(jù)進(jìn)行查找的動(dòng)作,所以負(fù)載因子是不適宜設(shè)置過(guò)高
get(key)
接下來(lái)看看get(key)做了什么
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (EntryK,V e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
這些動(dòng)作似乎是跟put(key,value)相識(shí),通過(guò)hash算法獲取key的hash碼,再通過(guò)indexFor定位出該key存在于table的哪一個(gè)下表,獲取該下標(biāo)然后對(duì)下標(biāo)中的鏈表進(jìn)行遍歷比對(duì),如果有符合就直接返回該key的value值。
keySet()
這里還涉及另一個(gè)問(wèn)題,上面說(shuō)了HashMap是跟set沒(méi)有任何親屬關(guān)系,但map也一樣實(shí)現(xiàn)了keySet接口,下面譜析一下keySet在hashMap中是如何實(shí)現(xiàn)的,這里給出部分代碼,請(qǐng)結(jié)合源碼查看
public K next() {
return nextEntry().getKey();
}
final EntryK,V nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
EntryK,V e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index t.length (next = t[index++]) == null)
;
}
current = e;
return e;
}
代碼很簡(jiǎn)單,就是對(duì)每個(gè)格子里面的鏈表進(jìn)行遍歷,也正是這個(gè)原因,當(dāng)我們依次將key值put進(jìn)hashMap中,但在使用map.entrySet().iterator()進(jìn)行遍歷時(shí)候卻不是put時(shí)候的順序。
擴(kuò)容
在前面說(shuō)到put函數(shù)的時(shí)候,已經(jīng)提過(guò)了擴(kuò)容的問(wèn)題
if (size++ = threshold)
resize(2 * table.length);
這里一個(gè)是否擴(kuò)容的判斷,當(dāng)數(shù)據(jù)達(dá)到了threshold所謂的重構(gòu)因子,而不是HashMap的最大容量,就進(jìn)行擴(kuò)容。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j src.length; j++) {
EntryK,V e = src[j];
if (e != null) {
src[j] = null;
do {
EntryK,V next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
transfer方法實(shí)際上是將所有的元素重新進(jìn)行一些hash,這是因?yàn)槿萘孔兓?,每個(gè)元素相對(duì)應(yīng)的hash值也會(huì)不一樣。
使用HashMap
1.不要再高并發(fā)中使用HashMap,HashMap是線程不安全,如果被多個(gè)線程共享之后,將可能發(fā)生不可預(yù)知的問(wèn)題。
2.如果數(shù)據(jù)大小事固定的,最好在初始化的時(shí)候就給HashMap一個(gè)合理的容量值,如果使用new HashMap()默認(rèn)構(gòu)造函數(shù),重構(gòu)因子的值是16*0.75=12,當(dāng)HashMap的容量超過(guò)了12后,就會(huì)進(jìn)行一系列的擴(kuò)容運(yùn)算,重建一個(gè)原來(lái)成倍的數(shù)組,并且對(duì)原來(lái)存在的元素進(jìn)行重新的hash運(yùn)算,如果你的數(shù)據(jù)是有成千上萬(wàn)的,那么你的成千上萬(wàn)的數(shù)據(jù)也要跟這你的擴(kuò)容不斷的hash,這將產(chǎn)生高額的內(nèi)存和cpu的大量開(kāi)銷。
當(dāng)然啦,HashMap的函數(shù)還有很多,不過(guò)都是基于table的鏈表進(jìn)行操作,當(dāng)然也就是hash算法,Map hashMap在平時(shí)我們的應(yīng)用非常多,最重要的是我們要對(duì)每句代碼中每塊數(shù)據(jù)結(jié)構(gòu)變化心中有數(shù)。
上面主要是參考了jdk源碼,數(shù)據(jù)結(jié)構(gòu)和一些相關(guān)資料本著好記性不如爛博客的精神記錄下來(lái),希望朋友們?nèi)绻l(fā)覺(jué)哪里不對(duì)請(qǐng)指出來(lái),虛心請(qǐng)教
編輯詞條JAVA容器
解釋一:
容器(Container)
Spring 提供容器功能,容器可以管理對(duì)象的生命周期、對(duì)象與對(duì)象之間的依賴關(guān)系,您可以使用一個(gè)配置文件(通常是XML),在上面定義好對(duì)象的名稱、如何產(chǎn)生(Prototype 方式或Singleton 方式)、哪個(gè)對(duì)象產(chǎn)生之后必須設(shè)定成為某個(gè)對(duì)象的屬性等,在啟動(dòng)容器之后,所有的對(duì)象都可以直接取用,不用編寫(xiě)任何一行程序代碼來(lái)產(chǎn)生對(duì)象,或是建立對(duì)象與對(duì)象之間的依賴關(guān)系。
換個(gè)更直白點(diǎn)的說(shuō)明方式:容器是一個(gè)Java 所編寫(xiě)的程序,原先必須自行編寫(xiě)程序以管理對(duì)象關(guān)系,現(xiàn)在容器都會(huì)自動(dòng)幫您作好。
常用容器:WebSphere,WebLogic,Resin,Tomcat
----------------------------------
解釋二:
容器類
Java容器類包含List、ArrayList、Vector及map、HashTable、HashMap
ArrayList和HashMap是異步的,Vector和HashTable是同步的,所以Vector和HashTable是線程安全的,而 ArrayList和HashMap并不是線程安全的。因?yàn)橥叫枰ㄙM(fèi)機(jī)器時(shí)間,所以Vector和HashTable的執(zhí)行效率要低于 ArrayList和HashMap。
Collection
├List 接口
│├LinkedList 鏈表
│├ArrayList 順序結(jié)構(gòu)動(dòng)態(tài)數(shù)組類
│└Vector 向量
│ └Stack 棧
└Set
Map
├Hashtable
├HashMap
└WeakHashMap List接口
List是有序的Collection,使用此接口能夠精確的控制每個(gè)元素插入的位置。用戶能夠使用索引(元素在List中的位置,類似于數(shù)組下標(biāo))來(lái)訪問(wèn)List中的元素,這類似于Java的數(shù)組。
和下面要提到的Set不同,List允許有相同的元素。
除了具有Collection接口必備的iterator()方法外,List還提供一個(gè)listIterator()方法,返回一個(gè) ListIterator接口,和標(biāo)準(zhǔn)的Iterator接口相比,ListIterator多了一些add()之類的方法,允許添加,刪除,設(shè)定元素, 還能向前或向后遍歷。
實(shí)現(xiàn)List接口的常用類有LinkedList,ArrayList,Vector和Stack。
ArrayList類
ArrayList實(shí)現(xiàn)了可變大小的數(shù)組。它允許所有元素,包括null。ArrayList沒(méi)有同步。
size,isEmpty,get,set方法運(yùn)行時(shí)間為常數(shù)。但是add方法開(kāi)銷為分?jǐn)偟某?shù),添加n個(gè)元素需要O(n)的時(shí)間。其他的方法運(yùn)行時(shí)間為線性。
每個(gè)ArrayList實(shí)例都有一個(gè)容量(Capacity),即用于存儲(chǔ)元素的數(shù)組的大小。這個(gè)容量可隨著不斷添加新元素而自動(dòng)增加,但是增長(zhǎng)算法 并沒(méi)有定義。當(dāng)需要插入大量元素時(shí),在插入前可以調(diào)用ensureCapacity方法來(lái)增加ArrayList的容量以提高插入效率。
和LinkedList一樣,ArrayList也是非同步的(unsynchronized)。
Map接口
請(qǐng)注意,Map沒(méi)有繼承Collection接口,Map提供key到value的映射。一個(gè)Map中不能包含相同的key,每個(gè)key只能映射一個(gè) value。Map接口提供3種集合的視圖,Map的內(nèi)容可以被當(dāng)作一組key集合,一組value集合,或者一組key-value映射。
HashMap類
HashMap和Hashtable類似,不同之處在于HashMap是非同步的,并且允許null,即null value和null key。,但是將HashMap視為Collection時(shí)(values()方法可返回Collection),其迭代子操作時(shí)間開(kāi)銷和HashMap 的容量成比例。因此,如果迭代操作的性能相當(dāng)重要的話,不要將HashMap的初始化容量設(shè)得過(guò)高,或者load factor過(guò)低。
Collection接口
Collection是最基本的集合接口,一個(gè)Collection代表一組Object,即Collection的元素(Elements)。一些Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接繼承自Collection的類,Java SDK提供的類都是繼承自Collection的“子接口”如List和Set。
所有實(shí)現(xiàn)Collection接口的類都必須提供兩個(gè)標(biāo)準(zhǔn)的構(gòu)造函數(shù):無(wú)參數(shù)的構(gòu)造函數(shù)用于創(chuàng)建一個(gè)空的Collection,有一個(gè)Collection參數(shù)的構(gòu)造函數(shù)用于創(chuàng)建一個(gè)新的Collection,這個(gè)新的Collection與傳入的Collection有相同的元素。后一個(gè)構(gòu)造函數(shù)允許用戶復(fù)制一個(gè)Collection。
如何遍歷Collection中的每一個(gè)元素?不論Collection的實(shí)際類型如何,它都支持一個(gè)iterator()的方法,該方法返回一個(gè)迭代子,使用該迭代子即可逐一訪問(wèn)Collection中每一個(gè)元素。典型的用法如下:
Iterator it = collection.iterator(); // 獲得一個(gè)迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一個(gè)元素
}
由Collection接口派生的兩個(gè)接口是List和Set。
Hashtable類
Hashtable繼承Map接口,實(shí)現(xiàn)一個(gè)key-value映射的哈希表。任何非空(non-null)的對(duì)象都可作為key或者value。
添加數(shù)據(jù)使用put(key, value),取出數(shù)據(jù)使用get(key),這兩個(gè)基本操作的時(shí)間開(kāi)銷為常數(shù)。
Hashtable通過(guò)initial capacity和load factor兩個(gè)參數(shù)調(diào)整性能。通常缺省的load factor 0.75較好地實(shí)現(xiàn)了時(shí)間和空間的均衡。增大load factor可以節(jié)省空間但相應(yīng)的查找時(shí)間將增大,這會(huì)影響像get和put這樣的操作。
使用Hashtable的簡(jiǎn)單示例如下,將1,2,3放到Hashtable中,他們的key分別是”one”,”two”,”three”:
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));
要取出一個(gè)數(shù),比如2,用相應(yīng)的key:
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
由于作為key的對(duì)象將通過(guò)計(jì)算其散列函數(shù)來(lái)確定與之對(duì)應(yīng)的value的位置,因此任何作為key的對(duì)象都必須實(shí)現(xiàn)hashCode和equals方法。hashCode和equals方法繼承自根類Object,如果你用自定義的類當(dāng)作key的話,要相當(dāng)小心,按照散列函數(shù)的定義,如果兩個(gè)對(duì)象相同,即obj1.equals(obj2)=true,則它們的hashCode必須相同,但如果兩個(gè)對(duì)象不同,則它們的hashCode不一定不同,如果兩個(gè)不同對(duì)象的hashCode相同,這種現(xiàn)象稱為沖突,沖突會(huì)導(dǎo)致操作哈希表的時(shí)間開(kāi)銷增大,所以盡量定義好的hashCode()方法,能加快哈希表的操作。
如果相同的對(duì)象有不同的hashCode,對(duì)哈希表的操作會(huì)出現(xiàn)意想不到的結(jié)果(期待的get方法返回null),要避免這種問(wèn)題,只需要牢記一條:要同時(shí)復(fù)寫(xiě)equals方法和hashCode方法,而不要只寫(xiě)其中一個(gè)。
Hashtable是同步的。
HashMap類
HashMap和Hashtable類似,不同之處在于HashMap是非同步的,并且允許null,即null value和null key。,但是將HashMap視為Collection時(shí)(values()方法可返回Collection),其迭代子操作時(shí)間開(kāi)銷和HashMap的容量成比例。因此,如果迭代操作的性能相當(dāng)重要的話,不要將HashMap的初始化容量設(shè)得過(guò)高,或者load factor過(guò)低。
WeakHashMap類
WeakHashMap是一種改進(jìn)的HashMap,它對(duì)key實(shí)行“弱引用”,如果一個(gè)key不再被外部所引用,那么該key可以被GC回收。
總結(jié)
如果涉及到堆棧,隊(duì)列等操作,應(yīng)該考慮用List,對(duì)于需要快速插入,刪除元素,應(yīng)該使用LinkedList,如果需要快速隨機(jī)訪問(wèn)元素,應(yīng)該使用ArrayList。
如果程序在單線程環(huán)境中,或者訪問(wèn)僅僅在一個(gè)線程中進(jìn)行,考慮非同步的類,其效率較高,如果多個(gè)線程可能同時(shí)操作一個(gè)類,應(yīng)該使用同步的類。
要特別注意對(duì)哈希表的操作,作為key的對(duì)象要正確復(fù)寫(xiě)equals和hashCode方法。
盡量返回接口而非實(shí)際的類型,如返回List而非ArrayList,這樣如果以后需要將ArrayList換成LinkedList時(shí),客戶端代碼不用改變。這就是針對(duì)抽象編程。
同步性
Vector是同步的。這個(gè)類中的一些方法保證了Vector中的對(duì)象是線程安全的。而ArrayList則是異步的,因此ArrayList中的對(duì)象并不是線程安全的。因?yàn)橥降囊髸?huì)影響執(zhí)行的效率,所以如果你不需要線程安全的集合那么使用ArrayList是一個(gè)很好的選擇,這樣可以避免由于同步帶來(lái)的不必要的性能開(kāi)銷。
數(shù)據(jù)增長(zhǎng)
從內(nèi)部實(shí)現(xiàn)機(jī)制來(lái)講ArrayList和Vector都是使用數(shù)組(Array)來(lái)控制集合中的對(duì)象。當(dāng)你向這兩種類型中增加元素的時(shí)候,如果元素的數(shù)目超出了內(nèi)部數(shù)組目前的長(zhǎng)度它們都需要擴(kuò)展內(nèi)部數(shù)組的長(zhǎng)度,Vector缺省情況下自動(dòng)增長(zhǎng)原來(lái)一倍的數(shù)組長(zhǎng)度,ArrayList是原來(lái)的50%,所以最后你獲得的這個(gè)集合所占的空間總是比你實(shí)際需要的要大。所以如果你要在集合中保存大量的數(shù)據(jù)那么使用Vector有一些優(yōu)勢(shì),因?yàn)槟憧梢酝ㄟ^(guò)設(shè)置集合的初始化大小來(lái)避免不必要的資源開(kāi)銷。
使用模式
在ArrayList和Vector中,從一個(gè)指定的位置(通過(guò)索引)查找數(shù)據(jù)或是在集合的末尾增加、移除一個(gè)元素所花費(fèi)的時(shí)間是一樣的,這個(gè)時(shí)間我們用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花費(fèi)的時(shí)間會(huì)呈線形增長(zhǎng):O(n-i),其中n代表集合中元素的個(gè)數(shù),i代表元素增加或移除元素的索引位置。為什么會(huì)這樣呢?以為在進(jìn)行上述操作的時(shí)候集合中第i和第i個(gè)元素之后的所有元素都要執(zhí)行位移的操作。這一切意味著什么呢?
這意味著,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好選擇其他的集合操作類。比如,LinkList集合類在增加或移除集合中任何位置的元素所花費(fèi)的時(shí)間都是一樣的?O(1),但它在索引一個(gè)元素的使用缺比較慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因?yàn)槟憧梢院?jiǎn)單的使用索引來(lái)代替創(chuàng)建iterator對(duì)象的操作。LinkList也會(huì)為每個(gè)插入的元素創(chuàng)建對(duì)象,所有你要明白它也會(huì)帶來(lái)額外的開(kāi)銷。
最后,在《Practical Java》一書(shū)中Peter Haggar建議使用一個(gè)簡(jiǎn)單的數(shù)組(Array)來(lái)代替Vector或ArrayList。尤其是對(duì)于執(zhí)行效率要求高的程序更應(yīng)如此。因?yàn)槭褂脭?shù)組(Array)避免了同步、額外的方法調(diào)用和不必要的重新分配空間的操作。
理解集合類
集合類存放于java.util包中。
集合類存放的都是對(duì)象的引用,而非對(duì)象本身,出于表達(dá)上的便利,我們稱集合中的對(duì)象就是指集合中對(duì)象的引用(reference)。
集合類型主要有3種:set(集)、list(列表)和map(映射)。
(1)集
集(set)是最簡(jiǎn)單的一種集合,它的對(duì)象不按特定方式排序,只是簡(jiǎn)單的把對(duì)象加入集合中,就像往口袋里放東西。
對(duì)集中成員的訪問(wèn)和操作是通過(guò)集中對(duì)象的引用進(jìn)行的,所以集中不能有重復(fù)對(duì)象。
集也有多種變體,可以實(shí)現(xiàn)排序等功能,如TreeSet,它把對(duì)象添加到集中的操作將變?yōu)榘凑漳撤N比較規(guī)則將其插入到有序的對(duì)象序列中。它實(shí)現(xiàn)的是SortedSet接口,也就是加入了對(duì)象比較的方法。通過(guò)對(duì)集中的對(duì)象迭代,我們可以得到一個(gè)升序的對(duì)象集合。
(2)列表
列表的主要特征是其對(duì)象以線性方式存儲(chǔ),沒(méi)有特定順序,只有一個(gè)開(kāi)頭和一個(gè)結(jié)尾,當(dāng)然,它與根本沒(méi)有順序的集是不同的。
列表在數(shù)據(jù)結(jié)構(gòu)中分別表現(xiàn)為:數(shù)組和向量、鏈表、堆棧、隊(duì)列。
關(guān)于實(shí)現(xiàn)列表的集合類,是我們?nèi)粘9ぷ髦薪?jīng)常用到的,將在后邊的筆記詳細(xì)介紹。
(3)映射
映射與集或列表有明顯區(qū)別,映射中每個(gè)項(xiàng)都是成對(duì)的。映射中存儲(chǔ)的每個(gè)對(duì)象都有一個(gè)相關(guān)的關(guān)鍵字(Key)對(duì)象,關(guān)鍵字決定了對(duì)象在映射中的存儲(chǔ)位置,檢索對(duì)象時(shí)必須提供相應(yīng)的關(guān)鍵字,就像在字典中查單詞一樣。關(guān)鍵字應(yīng)該是唯一的。
關(guān)鍵字本身并不能決定對(duì)象的存儲(chǔ)位置,它需要對(duì)過(guò)一種散列(hashing)技術(shù)來(lái)處理,產(chǎn)生一個(gè)被稱作散列碼(hash code)的整數(shù)值,散列碼通常用作一個(gè)偏置量,該偏置量是相對(duì)于分配給映射的內(nèi)存區(qū)域起始位置的,由此確定關(guān)鍵字/對(duì)象對(duì)的存儲(chǔ)位置。理想情況下,散列處理應(yīng)該產(chǎn)生給定范圍內(nèi)均勻分布的值,而且每個(gè)關(guān)鍵字應(yīng)得到不同的散列碼。
集合類簡(jiǎn)介
java.util中共有13個(gè)類可用于管理集合對(duì)象,它們支持集、列表或映射等集合,以下是這些類的簡(jiǎn)單介紹
集:
HashSet: 使用HashMap的一個(gè)集的實(shí)現(xiàn)。雖然集定義成無(wú)序,但必須存在某種方法能相當(dāng)高效地找到一個(gè)對(duì)象。使用一個(gè)HashMap對(duì)象實(shí)現(xiàn)集的存儲(chǔ)和檢索操作是在固定時(shí)間內(nèi)實(shí)現(xiàn)的.
TreeSet: 在集中以升序?qū)?duì)象排序的集的實(shí)現(xiàn)。這意味著從一個(gè)TreeSet對(duì)象獲得第一個(gè)迭代器將按升序提供對(duì)象。TreeSet類使用了一個(gè)TreeMap.
列表:
Vector: 實(shí)現(xiàn)一個(gè)類似數(shù)組一樣的表,自動(dòng)增加容量來(lái)容納你所需的元素。使用下標(biāo)存儲(chǔ)和檢索對(duì)象就象在一個(gè)標(biāo)準(zhǔn)的數(shù)組中一樣。你也可以用一個(gè)迭代器從一個(gè)Vector中檢索對(duì)象。Vector是唯一的同步容器類??當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問(wèn)時(shí)也是性能良好的。
Stack: 這個(gè)類從Vector派生而來(lái),并且增加了方法實(shí)現(xiàn)棧??一種后進(jìn)先出的存儲(chǔ)結(jié)構(gòu)。
LinkedList: 實(shí)現(xiàn)一個(gè)鏈表。由這個(gè)類定義的鏈表也可以像?;蜿?duì)列一樣被使用。
ArrayList: 實(shí)現(xiàn)一個(gè)數(shù)組,它的規(guī)??勺儾⑶夷芟矜湵硪粯颖辉L問(wèn)。它提供的功能類似Vector類但不同步。
映射:
HashTable: 實(shí)現(xiàn)一個(gè)映象,所有的鍵必須非空。為了能高效的工作,定義鍵的類必須實(shí)現(xiàn)hashcode()方法和equal()方法。這個(gè)類是前面java實(shí)現(xiàn)的一個(gè)繼承,并且通常能在實(shí)現(xiàn)映象的其他類中更好的使用。
HashMap: 實(shí)現(xiàn)一個(gè)映象,允許存儲(chǔ)空對(duì)象,而且允許鍵是空(由于鍵必須是唯一的,當(dāng)然只能有一個(gè))。
WeakHashMap: 實(shí)現(xiàn)這樣一個(gè)映象:通常如果一個(gè)鍵對(duì)一個(gè)對(duì)象而言不再被引用,鍵/對(duì)象對(duì)將被舍棄。這與HashMap形成對(duì)照,映象中的鍵維持鍵/對(duì)象對(duì)的生命周期,盡管使用映象的程序不再有對(duì)鍵的引用,并且因此不能檢索對(duì)象。
TreeMap: 實(shí)現(xiàn)這樣一個(gè)映象,對(duì)象是按鍵升序排列的。
Set和List都是由公共接口Collection擴(kuò)展而來(lái),所以它們都可以使用一個(gè)類型為Collection的變量來(lái)引用。這就意味著任何列表或集構(gòu)成的集合都可以用這種方式引用,只有映射類除外(但也不是完全排除在外,因?yàn)榭梢詮挠成浍@得一個(gè)列表。)所以說(shuō),把一個(gè)列表或集傳遞給方法的標(biāo)準(zhǔn)途徑是使用Collection類型的參數(shù)。
另外,站長(zhǎng)團(tuán)上有產(chǎn)品團(tuán)購(gòu),便宜有保證
上期使用 紅黑樹(shù) 實(shí)現(xiàn)映射結(jié)構(gòu),這樣的結(jié)構(gòu)滿足 Key 必須具備可比性,元素有順序地分布 這兩個(gè)特點(diǎn)。在實(shí)際的應(yīng)用場(chǎng)景中,存在結(jié)構(gòu)中的 元素是不需要有序的,并且 Key 也不具備可比較性 ,哈希表完全滿足這樣的應(yīng)用場(chǎng)景。
比如設(shè)計(jì)一個(gè)公司的通訊錄,存放所有員工的通訊信息,就可以拿手機(jī)號(hào)作為 index,員工的名稱、職位等作為 value。用哈希表的方式可以將添加、刪除和搜索的時(shí)間復(fù)雜度控制在 O(1)。
這時(shí)創(chuàng)建一個(gè)數(shù)組,手機(jī)號(hào)作為 index,然后存放 value。這樣能將復(fù)雜度控制在 O(1),但是這種 空間換時(shí)間 的方式也造成了一些其他問(wèn)題,比如空間復(fù)雜度大(需要更多的空間),空間使用率極其低,非常浪費(fèi)內(nèi)存空間。
哈希表 就是空間換時(shí)間的處理方式,但是做了優(yōu)化,在空間和時(shí)間兩個(gè)緯度中達(dá)到適當(dāng)?shù)钠胶狻?/p>
哈希表也叫做散列表,整體結(jié)構(gòu)就是一個(gè)數(shù)組 ,哈希表會(huì)將 key 用哈希函數(shù)處理之后返回 hash(哈希值),hash 就是哈希表中的 index這樣的處理方式就可以滿足搜索時(shí)間是 O(1),這樣的處理方式就可以滿足搜索時(shí)間是 O(1)。因?yàn)楣1碇械?key 可能不具備可比較性,所以要做哈希處理。
在執(zhí)行哈希函數(shù)之后返回的 hash,可能會(huì)出現(xiàn)相同的情況 ,這樣的情況就是 哈希沖突 。解決哈希沖突常見(jiàn)的方法有這三種:
JDK1.8 解決哈希沖突的方式就是使用鏈地址法,其中的鏈表就是通過(guò)鏈表+紅黑樹(shù)的組合來(lái)實(shí)現(xiàn) 。比如當(dāng)哈希表中的容量大于等于 64,并且單向鏈表的節(jié)點(diǎn)數(shù)大于 8 時(shí),轉(zhuǎn)換為紅黑樹(shù),不滿足這個(gè)條件時(shí)就使用單向鏈表。
哈希函數(shù) 是生成哈希值的實(shí)現(xiàn)方法,哈希函數(shù)的實(shí)現(xiàn)步驟大致分為兩步:
hash_code 是生成哈希值的函數(shù),也可以直接用 JAVA 中的標(biāo)準(zhǔn)函數(shù) hashCode() 。
這里可以用 位運(yùn)算替換 % 運(yùn)算,來(lái)提高效率。因?yàn)? 位運(yùn)算是二進(jìn)制運(yùn)算,所以在設(shè)計(jì)數(shù)組的時(shí)候,需要將數(shù)組的長(zhǎng)度設(shè)計(jì)為 2 的冪次方。
一個(gè)良好的哈希函數(shù),可以讓生成的哈希值分布更加均勻,減少哈希沖突的次數(shù),最終可以提升哈希表的性能。
Key 的常見(jiàn)類型可能有證書(shū)、浮點(diǎn)數(shù)、字符串或者自定義對(duì)象,不同的類型生成哈希值的方式也會(huì)不一樣,但是目標(biāo)是一致的,就是 盡量讓每個(gè) Key 的哈希值唯一,盡量讓 Key 中的所有信息參與運(yùn)算 。
比如在 Java 中, Long 的哈希值實(shí)現(xiàn)如下代碼:
這里的 和 ^ 就是將高 32 bit 和低 32 bit 混合計(jì)算出 32 bit 的哈希值。
在計(jì)算字符串的哈希值時(shí),可以將字符串拆解成若干個(gè)字符,比如 jack,將它拆解成 j、a、c、k(字符的本質(zhì)就是一個(gè)整數(shù),所以 jack 的哈希值可以表示為 j * n3 + a * n2 + c * n1 + k * n0,表達(dá)式也可以寫(xiě)成 [(j * n + a) * n + c] * n + k,代碼實(shí)現(xiàn)如下:
看上面代碼時(shí),可以發(fā)現(xiàn),表達(dá)式中的 n 使用的是 31 這個(gè)數(shù)字,那么為什么用 31 呢?
因?yàn)?31 不僅符合 22 - 1 , 而且它還是個(gè)奇素?cái)?shù)(既是技術(shù),又是素?cái)?shù),還是質(zhì)數(shù)),素?cái)?shù)和其他數(shù)相乘的結(jié)果比其他方式更容易產(chǎn)生唯一性,減少哈希沖突。
JDK 中,乘數(shù) n 也是用 31,31 也是經(jīng)過(guò)觀測(cè)分布結(jié)果后的選擇,關(guān)于 31 的變體可以有以下幾種:
31 * i = (25 - 1) * i = i * 25 - i = (i 5) - i
#include stdio.h
#include string.h
#include stdlib.h
//#include
#define HASH_LEN 50 //哈希表的長(zhǎng)度
#define M 47
#define NAME_NO 30 //人名的個(gè)數(shù)
typedef struct NAME
{
char *py; //名字的拼音
int k; //拼音所對(duì)應(yīng)的整數(shù)
}NAME;
NAME NameList[HASH_LEN];
typedef struct hterm //哈希表
{
char *py; //名字的拼音
int k; //拼音所對(duì)應(yīng)的整數(shù)
int si; //查找長(zhǎng)度
}HASH;
HASH HashList[HASH_LEN];
/*-----------------------姓名(結(jié)構(gòu)體數(shù)組)初始化---------------------------------*/
void InitNameList()
{
NameList[0].py="chenghongxiu";
NameList[1].py="yuanhao";
NameList[2].py="yangyang";
NameList[3].py="zhanghen";
NameList[4].py="chenghongxiu";
NameList[5].py="xiaokai";
NameList[6].py="liupeng";
NameList[7].py="shenyonghai";
NameList[8].py="chengdaoquan";
NameList[9].py="ludaoqing";
NameList[10].py="gongyunxiang";
NameList[11].py="sunzhenxing";
NameList[12].py="sunrongfei";
NameList[13].py="sunminglong";
NameList[14].py="zhanghao";
NameList[15].py="tianmiao";
NameList[16].py="yaojianzhong";
NameList[17].py="yaojianqing";
NameList[18].py="yaojianhua";
NameList[19].py="yaohaifeng";
NameList[20].py="chengyanhao";
NameList[21].py="yaoqiufeng";
NameList[22].py="qianpengcheng";
NameList[23].py="yaohaifeng";
NameList[24].py="bianyan";
NameList[25].py="linglei";
NameList[26].py="fuzhonghui";
NameList[27].py="huanhaiyan";
NameList[28].py="liudianqin";
NameList[29].py="wangbinnian";
char *f;
int r,s0;
for (int i=0;iNAME_NO;i++)
{
s0=0;
f=NameList[i].py;
for (r=0;*(f+r) != NULL;r++) //方法:將字符串的各個(gè)字符所對(duì)應(yīng)的ASCII碼相加,所得的整數(shù)做為哈希表的關(guān)鍵字
s0=*(f+r)+s0;
NameList[i].k=s0;
}
}
/*-----------------------建立哈希表---------------------------------*/
void CreateHashList()
{
for (int i=0; iNAME_NO; i ++)
{
HashList[i].py="";
HashList[i].k=0;
HashList[i].si=0;
}
for (i=0; i NAME_NO ; i++)
{
int sum=0;
int adr=(NameList[i].k) % M; //哈希函數(shù)
int d=adr;
if(HashList[adr].si==0) //如果不沖突
{
HashList[adr].k=NameList[i].k;
HashList[adr].py=NameList[i].py;
HashList[adr].si=1;
}
else //沖突
{
do{
d=(d+((NameList[i].k))%10+1)%M; //偽散列
sum=sum+1; //查找次數(shù)加1
}while (HashList[d].k!=0);
HashList[d].k=NameList[i].k;
HashList[d].py=NameList[i].py;
HashList[d].si=sum+1;
}
}
}
/*-------------------------------------查找------------------------------------*/
void FindList()
{
printf("\n\n請(qǐng)輸入姓名的拼音: "); //輸入姓名
char name[20]={0};
scanf("%s",name);
int s0=0;
for (int r=0;r20;r++) //求出姓名的拼音所對(duì)應(yīng)的整數(shù)(關(guān)鍵字)
s0+=name[r];
int sum=1;
int adr=s0 % M; //使用哈希函數(shù)
int d=adr;
if(HashList[adr].k==s0) //分3種情況進(jìn)行判斷
printf("\n姓名:%s 關(guān)鍵字:%d 查找長(zhǎng)度為: 1",HashList[d].py,s0);
else if (HashList[adr].k==0)
printf("無(wú)該記錄!");
else
{
int g=0;
do
{
d=(d+s0%10+1)%M; //偽散列
sum=sum+1;
if (HashList[d].k==0)
{
printf("無(wú)記錄! ");
g=1;
}
if (HashList[d].k==s0)
{
printf("\n姓名:%s 關(guān)鍵字:%d 查找長(zhǎng)度為:%d",HashList[d].py,s0,sum);
g=1;
}
}while(g==0);
}
}
/*--------------------------------顯示哈希表----------------------------*/
void Display()
{
printf("\n\n地址\t關(guān)鍵字\t\t搜索長(zhǎng)度\tH(key)\t\t拼音 \n"); //顯示的格式
for(int i=0; i15; i++)
{
printf("%d ",i);
printf("\t%d ",HashList[i].k);
printf("\t\t%d ",HashList[i].si);
printf("\t\t%d ",(HashList[i].k)%M);
printf("\t %s ",HashList[i].py);
printf("\n");
}
printf("按任意鍵繼續(xù)顯示...\n"); //由于數(shù)據(jù)比較多,所以分屏顯示(以便在Win9x/DOS下能看到所有的數(shù)據(jù))
getchar();
for( i=15; i30; i++)
{
printf("%d ",i);
printf("\t%d ",HashList[i].k);
printf("\t\t%d ",HashList[i].si);
printf("\t\t%d ",(HashList[i].k)%M);
printf("\t %s ",HashList[i].py);
printf("\n");
}
printf("按任意鍵繼續(xù)顯示...\n");
getchar();
for( i=30; i40; i++)
{
printf("%d ",i);
printf("\t%d ",HashList[i].k);
printf("\t\t%d ",HashList[i].si);
printf("\t\t%d ",(HashList[i].k)%M);
printf("\t %s ",HashList[i].py);
printf("\n");
}
printf("按任意鍵繼續(xù)顯示...\n");
getchar();
for( i=40; i50; i++)
{
printf("%d ",i);
printf("\t%d ",HashList[i].k);
printf("\t\t%d ",HashList[i].si);
printf("\t\t%d ",(HashList[i].k)%M);
printf("\t %s ",HashList[i].py);
printf("\n");
}
float average=0;
for (i=0;i NAME_NO;i ++)
average+=HashList[i].si;
average/=NAME_NO;
printf("\n\n平均查找長(zhǎng)度:ASL(%d)=%f \n\n",NAME_NO,average);
}
/*--------------------------------主函數(shù)----------------------------*/
void main()
{
/* ::SetConsoleTitle("哈希表操作"); //Windows API函數(shù),設(shè)置控制臺(tái)窗口的標(biāo)題
HANDLE hCon = ::GetStdHandle(STD_OUTPUT_HANDLE); //獲得標(biāo)準(zhǔn)輸出設(shè)備的句柄
::SetConsoleTextAttribute(hCon, 10|0); //設(shè)置文本顏色
*/
printf("\n------------------------哈希表的建立和查找----------------------");
InitNameList();
CreateHashList ();
while(1)
{
printf("\n\n");
printf(" 1. 顯示哈希表\n");
printf(" 2. 查找\n");
printf(" 3. 退出\n");
err:
char ch1=getchar();
if (ch1='1')
Display();
else if (ch1='2')
FindList();
else if (ch1='3')
return;
else
{
printf("\n請(qǐng)輸入正確的選擇!");
goto err;
}
}
}
HashMap是對(duì)數(shù)據(jù)結(jié)構(gòu)中哈希表(Hash
Table)的實(shí)現(xiàn),Hash表又叫散列表。Hash表是根據(jù)關(guān)鍵碼Key來(lái)訪問(wèn)其對(duì)應(yīng)的值Value的數(shù)據(jù)結(jié)構(gòu),它通過(guò)一個(gè)映射函數(shù)把關(guān)鍵碼映射到表中一個(gè)位置來(lái)訪問(wèn)該位置的值,從而加快查找的速度。這個(gè)映射函數(shù)叫做Hash函數(shù),存放記錄的數(shù)組叫做Hash表。
在Java中,HashMap的內(nèi)部實(shí)現(xiàn)結(jié)合了鏈表和數(shù)組的優(yōu)勢(shì),鏈接節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)是Entry
,每個(gè)Entry對(duì)象的內(nèi)部又含有指向下一個(gè)Entry類型對(duì)象的引用,如以下代碼所示:
static
class
Entry
implements
Map.Entry
{
final
K
key;
V
value;
Entry
next;
//Entry類型內(nèi)部有一個(gè)自己類型的引用,指向下一個(gè)Entry
final
int
hash;
...
}
在HashMap的構(gòu)造函數(shù)中可以看到,Entry表被申明為了數(shù)組,如以下代碼所示:
public
HashMap()
{
this.loadFactor
=
DEFAULT_LOAD_FACTOR;
threshold
=
(int)(DEFAULT_INITIAL_CAPACITY
*
DEFAULT_LOAD_FACTOR);
table
=
new
Entry[DEFAULT_INITIAL_CAPACITY];
init();
}
在以上構(gòu)造函數(shù)中,默認(rèn)的DEFAULT_INITIAL_CAPACITY值為16,DEFAULT_LOAD_FACTOR的值為0.75。
當(dāng)put一個(gè)元素到HashMap中去時(shí),其內(nèi)部實(shí)現(xiàn)如下:
public
V
put(K
key,
V
value)
{
if
(key
==
null)
return
putForNullKey(value);
int
hash
=
hash(key.hashCode());
int
i
=
indexFor(hash,
table.length);
...
}