Map 提供了一個更通用的元素存儲方法。 Map 集合類用于存儲元素對(稱作“鍵”和“值”),其中每個鍵映射到一個值。 從概念上而言,您可以將 List 看作是具有數(shù)值鍵的 Map。 而實(shí)際上,除了 List 和 Map 都在定義 java.util 中外,兩者并沒有直接的聯(lián)系。本文將著重介紹核心 Java 發(fā)行套件中附帶的 Map,同時還將介紹如何采用或?qū)崿F(xiàn)更適用于您應(yīng)用程序特定數(shù)據(jù)的專用 Map。
了解 Map 接口和方法
Java 核心類中有很多預(yù)定義的 Map 類。 在介紹具體實(shí)現(xiàn)之前,我們先介紹一下 Map 接口本身,以便了解所有實(shí)現(xiàn)的共同點(diǎn)。 Map 接口定義了四種類型的方法,每個 Map 都包含這些方法。 下面,我們從兩個普通的方法(表 1)開始對這些方法加以介紹。
表 1: 覆蓋的方法。 我們將這 Object 的這兩個方法覆蓋,以正確比較 Map 對象的等價性。 equals(Object o) 比較指定對象與此 Map 的等價性
hashCode() 返回此 Map 的哈希碼
Map 構(gòu)建
Map 定義了幾個用于插入和刪除元素的變換方法(表 2)。
表 2: Map 更新方法: 可以更改 Map 內(nèi)容。 clear() 從 Map 中刪除所有映射
remove(Object key) 從 Map 中刪除鍵和關(guān)聯(lián)的值
put(Object key, Object value) 將指定值與指定鍵相關(guān)聯(lián)
clear() 從 Map 中刪除所有映射
putAll(Map t) 將指定 Map 中的所有映射復(fù)制到此 map
盡管您可能注意到,縱然假設(shè)忽略構(gòu)建一個需要傳遞給 putAll() 的 Map 的開銷,使用 putAll() 通常也并不比使用大量的 put() 調(diào)用更有效率,但 putAll() 的存在一點(diǎn)也不稀奇。 這是因?yàn)椋琾utAll() 除了迭代 put() 所執(zhí)行的將每個鍵值對添加到 Map 的算法以外,還需要迭代所傳遞的 Map 的元素。 但應(yīng)注意,putAll() 在添加所有元素之前可以正確調(diào)整 Map 的大小,因此如果您未親自調(diào)整 Map 的大小(我們將對此進(jìn)行簡單介紹),則 putAll() 可能比預(yù)期的更有效。
查看 Map
迭代 Map 中的元素不存在直接了當(dāng)?shù)姆椒ā?如果要查詢某個 Map 以了解其哪些元素滿足特定查詢,或如果要迭代其所有元素(無論原因如何),則您首先需要獲取該 Map 的“視圖”。 有三種可能的視圖(參見表 3)
所有鍵值對 — 參見 entrySet()
所有鍵 — 參見 keySet()
所有值 — 參見 values()
前兩個視圖均返回 Set 對象,第三個視圖返回 Collection 對象。 就這兩種情況而言,問題到這里并沒有結(jié)束,這是因?yàn)槟鸁o法直接迭代 Collection 對象或 Set 對象。要進(jìn)行迭代,您必須獲得一個 Iterator 對象。 因此,要迭代 Map 的元素,必須進(jìn)行比較煩瑣的編碼
Iterator keyValuePairs = aMap.entrySet().iterator();
Iterator keys = aMap.keySet().iterator();
Iterator values = aMap.values().iterator();
值得注意的是,這些對象(Set、Collection 和 Iterator)實(shí)際上是基礎(chǔ) Map 的視圖,而不是包含所有元素的副本。 這使它們的使用效率很高。 另一方面,Collection 或 Set 對象的 toArray() 方法卻創(chuàng)建包含 Map 所有元素的數(shù)組對象,因此除了確實(shí)需要使用數(shù)組中元素的情形外,其效率并不高。
我運(yùn)行了一個小測試(隨附文件中的 Test1),該測試使用了 HashMap,并使用以下兩種方法對迭代 Map 元素的開銷進(jìn)行了比較:
int mapsize = aMap.size();
Iterator keyValuePairs1 = aMap.entrySet().iterator();
for (int i = 0; i < mapsize; i++)
{
Map.Entry entry = (Map.Entry) keyValuePairs1.next();
Object key = entry.getKey();
Object value = entry.getValue();
...
}
Object[] keyValuePairs2 = aMap.entrySet().toArray();
for (int i = 0; i < rem; i++) {
{
Map.Entry entry = (Map.Entry) keyValuePairs2[i];
Object key = entry.getKey();
Object value = entry.getValue();
...
}
此測試使用了兩種測量方法: 一種是測量迭代元素的時間,另一種測量使用 toArray 調(diào)用創(chuàng)建數(shù)組的其他開銷。 第一種方法(忽略創(chuàng)建數(shù)組所需的時間)表明,使用已從 toArray 調(diào)用中創(chuàng)建的數(shù)組迭代元素的速度要比使用 Iterator 的速度大約快 30%-60%。 但如果將使用 toArray 方法創(chuàng)建數(shù)組的開銷包含在內(nèi),則使用 Iterator 實(shí)際上要快 10%-20%。 因此,如果由于某種原因要創(chuàng)建一個集合元素的數(shù)組而非迭代這些元素,則應(yīng)使用該數(shù)組迭代元素。 但如果您不需要此中間數(shù)組,則不要創(chuàng)建它,而是使用 Iterator 迭代元素。
表 3: 返回視圖的 Map 方法: 使用這些方法返回的對象,您可以遍歷 Map 的元素,還可以刪除 Map 中的元素。 entrySet() 返回 Map 中所包含映射的 Set 視圖。 Set 中的每個元素都是一個 Map.Entry 對象,可以使用 getKey() 和 getValue() 方法(還有一個 setValue() 方法)訪問后者的鍵元素和值元素
keySet() 返回 Map 中所包含鍵的 Set 視圖。 刪除 Set 中的元素還將刪除 Map 中相應(yīng)的映射(鍵和值)
values() 返回 map 中所包含值的 Collection 視圖。 刪除 Collection 中的元素還將刪除 Map 中相應(yīng)的映射(鍵和值)
訪問元素
表 4 中列出了 Map 訪問方法。Map 通常適合按鍵(而非按值)進(jìn)行訪問。 Map 定義中沒有規(guī)定這肯定是真的,但通常您可以期望這是真的。 例如,您可以期望 containsKey() 方法與 get() 方法一樣快。 另一方面,containsValue() 方法很可能需要掃描 Map 中的值,因此它的速度可能比較慢。
表 4: Map 訪問和測試方法: 這些方法檢索有關(guān) Map 內(nèi)容的信息但不更改 Map 內(nèi)容。 get(Object key) 返回與指定鍵關(guān)聯(lián)的值
containsKey(Object key) 如果 Map 包含指定鍵的映射,則返回 true
containsValue(Object value) 如果此 Map 將一個或多個鍵映射到指定值,則返回 true
isEmpty() 如果 Map 不包含鍵-值映射,則返回 true
size() 返回 Map 中的鍵-值映射的數(shù)目
對使用 containsKey() 和 containsValue() 遍歷 HashMap 中所有元素所需時間的測試表明,containsValue() 所需的時間要長很多。 實(shí)際上要長幾個數(shù)量級! (參見圖 1 和圖 2,以及隨附文件中的 Test2)。 因此,如果 containsValue() 是應(yīng)用程序中的性能問題,它將很快顯現(xiàn)出來,并可以通過監(jiān)測您的應(yīng)用程序輕松地將其識別。 這種情況下,我相信您能夠想出一個有效的替換方法來實(shí)現(xiàn) containsValue() 提供的等效功能。 但如果想不出辦法,則一個可行的解決方案是再創(chuàng)建一個 Map,并將第一個 Map 的所有值作為鍵。 這樣,第一個 Map 上的 containsValue() 將成為第二個 Map 上更有效的 containsKey()。
圖 1: 使用 JDeveloper 創(chuàng)建并運(yùn)行 Map 測試類
圖 2: 在 JDeveloper 中使用執(zhí)行監(jiān)測器進(jìn)行的性能監(jiān)測查出應(yīng)用程序中的瓶頸
核心 Map
Java 自帶了各種 Map 類。 這些 Map 類可歸為三種類型:
通用 Map,用于在應(yīng)用程序中管理映射,通常在 java.util 程序包中實(shí)現(xiàn)
HashMap
Hashtable
Properties
LinkedHashMap
IdentityHashMap
TreeMap
WeakHashMap
ConcurrentHashMap
專用 Map,您通常不必親自創(chuàng)建此類 Map,而是通過某些其他類對其進(jìn)行訪問
java.util.jar.Attributes
javax.print.attribute.standard.PrinterStateReasons
java.security.Provider
java.awt.RenderingHints
javax.swing.UIDefaults
一個用于幫助實(shí)現(xiàn)您自己的 Map 類的抽象類
AbstractMap
內(nèi)部哈希: 哈希映射技術(shù)
幾乎所有通用 Map 都使用哈希映射。 這是一種將元素映射到數(shù)組的非常簡單的機(jī)制,您應(yīng)了解哈希映射的工作原理,以便充分利用 Map。
哈希映射結(jié)構(gòu)由一個存儲元素的內(nèi)部數(shù)組組成。 由于內(nèi)部采用數(shù)組存儲,因此必然存在一個用于確定任意鍵訪問數(shù)組的索引機(jī)制。 實(shí)際上,該機(jī)制需要提供一個小于數(shù)組大小的整數(shù)索引值。 該機(jī)制稱作哈希函數(shù)。 在 Java 基于哈希的 Map 中,哈希函數(shù)將對象轉(zhuǎn)換為一個適合內(nèi)部數(shù)組的整數(shù)。 您不必為尋找一個易于使用的哈希函數(shù)而大傷腦筋: 每個對象都包含一個返回整數(shù)值的 hashCode() 方法。 要將該值映射到數(shù)組,只需將其轉(zhuǎn)換為一個正值,然后在將該值除以數(shù)組大小后取余數(shù)即可。 以下是一個簡單的、適用于任何對象的 Java 哈希函數(shù)
int hashvalue = Maths.abs(key.hashCode()) % table.length;
(% 二進(jìn)制運(yùn)算符(稱作模)將左側(cè)的值除以右側(cè)的值,然后返回整數(shù)形式的余數(shù)。)
實(shí)際上,在 1.4 版發(fā)布之前,這就是各種基于哈希的 Map 類所使用的哈希函數(shù)。 但如果您查看一下代碼,您將看到
int hashvalue = (key.hashCode() & 0x7FFFFFFF) % table.length;
它實(shí)際上是使用更快機(jī)制獲取正值的同一函數(shù)。 在 1.4 版中,HashMap 類實(shí)現(xiàn)使用一個不同且更復(fù)雜的哈希函數(shù),該函數(shù)基于 Doug Lea 的 util.concurrent 程序包(稍后我將更詳細(xì)地再次介紹 Doug Lea 的類)。
圖 3: 哈希工作原理
該圖介紹了哈希映射的基本原理,但我們還沒有對其進(jìn)行詳細(xì)介紹。 我們的哈希函數(shù)將任意對象映射到一個數(shù)組位置,但如果兩個不同的鍵映射到相同的位置,情況將會如何? 這是一種必然發(fā)生的情況。 在哈希映射的術(shù)語中,這稱作沖突。 Map 處理這些沖突的方法是在索引位置處插入一個鏈接列表,并簡單地將元素添加到此鏈接列表。 因此,一個基于哈希的 Map 的基本 put() 方法可能如下所示
public Object put(Object key, Object value) {
//我們的內(nèi)部數(shù)組是一個 Entry 對象數(shù)組
//Entry[] table;
//獲取哈希碼,并映射到一個索引
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
//循環(huán)遍歷位于 table[index] 處的鏈接列表,以查明
//我們是否擁有此鍵項 — 如果擁有,則覆蓋它
for (Entry e = table[index] ; e != null ; e = e.next) {
//必須檢查鍵是否相等,原因是不同的鍵對象
//可能擁有相同的哈希
if ((e.hash == hash) && e.key.equals(key)) {
//這是相同鍵,覆蓋該值
//并從該方法返回 old 值
Object old = e.value;
e.value = value;
return old;
}
}
//仍然在此處,因此它是一個新鍵,只需添加一個新 Entry
//Entry 對象包含 key 對象、 value 對象、一個整型的 hash、
//和一個指向列表中的下一個 Entry 的 next Entry
//創(chuàng)建一個指向上一個列表開頭的新 Entry,
//并將此新 Entry 插入表中
Entry e = new Entry(hash, key, value, table[index]);
table[index] = e;
return null;
}
如果看一下各種基于哈希的 Map 的源代碼,您將發(fā)現(xiàn)這基本上就是它們的工作原理。 此外,還有一些需要進(jìn)一步考慮的事項,如處理空鍵和值以及調(diào)整內(nèi)部數(shù)組。 此處定義的 put() 方法還包含相應(yīng) get() 的算法,這是因?yàn)椴迦氚ㄋ阉饔成渌饕幍捻椧圆槊髟撴I是否已經(jīng)存在。 (即 get() 方法與 put() 方法具有相同的算法,但 get() 不包含插入和覆蓋代碼。) 使用鏈接列表并不是解決沖突的唯一方法,某些哈希映射使用另一種“開放式尋址”方案,本文對其不予介紹。
優(yōu)化 Hasmap
如果哈希映射的內(nèi)部數(shù)組只包含一個元素,則所有項將映射到此數(shù)組位置,從而構(gòu)成一個較長的鏈接列表。 由于我們的更新和訪問使用了對鏈接列表的線性搜索,而這要比 Map 中的每個數(shù)組索引只包含一個對象的情形要慢得多,因此這樣做的效率很低。 訪問或更新鏈接列表的時間與列表的大小線性相關(guān),而使用哈希函數(shù)訪問或更新數(shù)組中的單個元素則與數(shù)組大小無關(guān) — 就漸進(jìn)性質(zhì)(Big-O 表示法)而言,前者為 O(n),而后者為 O(1)。 因此,使用一個較大的數(shù)組而不是讓太多的項聚集在太少的數(shù)組位置中是有意義的。
調(diào)整 Map 實(shí)現(xiàn)的大小
在哈希術(shù)語中,內(nèi)部數(shù)組中的每個位置稱作“存儲桶”(bucket),而可用的存儲桶數(shù)(即內(nèi)部數(shù)組的大小)稱作容量 (capacity)。 為使 Map 對象有效地處理任意數(shù)目的項,Map 實(shí)現(xiàn)可以調(diào)整自身的大小。 但調(diào)整大小的開銷很大。 調(diào)整大小需要將所有元素重新插入到新數(shù)組中,這是因?yàn)椴煌臄?shù)組大小意味著對象現(xiàn)在映射到不同的索引值。 先前沖突的鍵可能不再沖突,而先前不沖突的其他鍵現(xiàn)在可能沖突。 這顯然表明,如果將 Map 調(diào)整得足夠大,則可以減少甚至不再需要重新調(diào)整大小,這很有可能顯著提高速度。
使用 1.4.2 JVM 運(yùn)行一個簡單的測試,即用大量的項(數(shù)目超過一百萬)填充 HashMap。 表 5 顯示了結(jié)果,并將所有時間標(biāo)準(zhǔn)化為已預(yù)先設(shè)置大小的服務(wù)器模式(關(guān)聯(lián)文件中的 Test3)。 對于已預(yù)先設(shè)置大小的 JVM,客戶端和服務(wù)器模式 JVM 運(yùn)行時間幾乎相同(在放棄 JIT 編譯階段后)。 但使用 Map 的默認(rèn)大小將引發(fā)多次調(diào)整大小操作,開銷很大,在服務(wù)器模式下要多用 50% 的時間,而在客戶端模式下幾乎要多用兩倍的時間!
表 5: 填充已預(yù)先設(shè)置大小的 HashMap 與填充默認(rèn)大小的 HashMap 所需時間的比較 客戶端模式 服務(wù)器模式
預(yù)先設(shè)置的大小 100% 100%
默認(rèn)大小 294% 157%
使用負(fù)載因子
為確定何時調(diào)整大小,而不是對每個存儲桶中的鏈接列表的深度進(jìn)行記數(shù),基于哈希的 Map 使用一個額外參數(shù)并粗略計算存儲桶的密度。 Map 在調(diào)整大小之前,使用名為“負(fù)載因子”的參數(shù)指示 Map 將承擔(dān)的“負(fù)載”量,即它的負(fù)載程度。 負(fù)載因子、項數(shù)(Map 大小)與容量之間的關(guān)系簡單明了:
如果(負(fù)載因子)x(容量)>(Map 大小),則調(diào)整 Map 大小
例如,如果默認(rèn)負(fù)載因子為 0.75,默認(rèn)容量為 11,則 11 x 0.75 = 8.25,該值向下取整為 8 個元素。 因此,如果將第 8 個項添加到此 Map,則該 Map 將自身的大小調(diào)整為一個更大的值。 相反,要計算避免調(diào)整大小所需的初始容量,用將要添加的項數(shù)除以負(fù)載因子,并向上取整,例如,
對于負(fù)載因子為 0.75 的 100 個項,應(yīng)將容量設(shè)置為 100/0.75 = 133.33,并將結(jié)果向上取整為 134(或取整為 135 以使用奇數(shù))
奇數(shù)個存儲桶使 map 能夠通過減少沖突數(shù)來提高執(zhí)行效率。 雖然我所做的測試(關(guān)聯(lián)文件中的Test4)并未表明質(zhì)數(shù)可以始終獲得更好的效率,但理想情形是容量取質(zhì)數(shù)。 1.4 版后的某些 Map(如 HashMap 和 LinkedHashMap,而非 Hashtable 或 IdentityHashMap)使用需要 2 的冪容量的哈希函數(shù),但下一個最高 2 的冪容量由這些 Map 計算,因此您不必親自計算。
負(fù)載因子本身是空間和時間之間的調(diào)整折衷。 較小的負(fù)載因子將占用更多的空間,但將降低沖突的可能性,從而將加快訪問和更新的速度。 使用大于 0.75 的負(fù)載因子可能是不明智的,而使用大于 1.0 的負(fù)載因子肯定是不明知的,這是因?yàn)檫@必定會引發(fā)一次沖突。 使用小于 0.50 的負(fù)載因子好處并不大,但只要您有效地調(diào)整 Map 的大小,應(yīng)不會對小負(fù)載因子造成性能開銷,而只會造成內(nèi)存開銷。 但較小的負(fù)載因子將意味著如果您未預(yù)先調(diào)整 Map 的大小,則導(dǎo)致更頻繁的調(diào)整大小,從而降低性能,因此在調(diào)整負(fù)載因子時一定要注意這個問題。
選擇適當(dāng)?shù)?Map
應(yīng)使用哪種 Map? 它是否需要同步? 要獲得應(yīng)用程序的最佳性能,這可能是所面臨的兩個最重要的問題。 當(dāng)使用通用 Map 時,調(diào)整 Map 大小和選擇負(fù)載因子涵蓋了 Map 調(diào)整選項。
以下是一個用于獲得最佳 Map 性能的簡單方法
將您的所有 Map 變量聲明為 Map,而不是任何具體實(shí)現(xiàn),即不要聲明為 HashMap 或 Hashtable,或任何其他 Map 類實(shí)現(xiàn)。
Map criticalMap = new HashMap(); //好
HashMap criticalMap = new HashMap(); //差
這使您能夠只更改一行代碼即可非常輕松地替換任何特定的 Map 實(shí)例。
下載 Doug Lea 的 util.concurrent 程序包 (http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html)。 將 ConcurrentHashMap 用作默認(rèn) Map。 當(dāng)移植到 1.5 版時,將 java.util.concurrent.ConcurrentHashMap 用作您的默認(rèn) Map。 不要將 ConcurrentHashMap 包裝在同步的包裝器中,即使它將用于多個線程。 使用默認(rèn)大小和負(fù)載因子。
監(jiān)測您的應(yīng)用程序。 如果發(fā)現(xiàn)某個 Map 造成瓶頸,則分析造成瓶頸的原因,并部分或全部更改該 Map 的以下內(nèi)容: Map 類;Map 大小;負(fù)載因子;關(guān)鍵對象 equals() 方法實(shí)現(xiàn)。 專用的 Map 的基本上都需要特殊用途的定制 Map 實(shí)現(xiàn),否則通用 Map 將實(shí)現(xiàn)您所需的性能目標(biāo)。
Map 選擇
也許您曾期望更復(fù)雜的考量,而這實(shí)際上是否顯得太容易? 好的,讓我們慢慢來。 首先,您應(yīng)使用哪種 Map? 答案很簡單: 不要為您的設(shè)計選擇任何特定的 Map,除非實(shí)際的設(shè)計需要指定一個特殊類型的 Map。 設(shè)計時通常不需要選擇具體的 Map 實(shí)現(xiàn)。 您可能知道自己需要一個 Map,但不知道使用哪種。 而這恰恰就是使用 Map 接口的意義所在。 直到需要時再選擇 Map 實(shí)現(xiàn) — 如果隨處使用“Map”聲明的變量,則更改應(yīng)用程序中任何特殊 Map 的 Map 實(shí)現(xiàn)只需要更改一行,這是一種開銷很少的調(diào)整選擇。 是否要使用默認(rèn)的 Map 實(shí)現(xiàn)? 我很快將談到這個問題。
同步 Map
同步與否有何差別? (對于同步,您既可以使用同步的 Map,也可以使用 Collections.synchronizedMap() 將未同步的 Map 轉(zhuǎn)換為同步的 Map。 后者使用“同步的包裝器”)這是一個異常復(fù)雜的選擇,完全取決于您如何根據(jù)多線程并發(fā)訪問和更新使用 Map,同時還需要進(jìn)行維護(hù)方面的考慮。 例如,如果您開始時未并發(fā)更新特定 Map,但它后來更改為并發(fā)更新,情況將如何? 在這種情況下,很容易在開始時使用一個未同步的 Map,并在后來向應(yīng)用程序中添加并發(fā)更新線程時忘記將此未同步的 Map 更改為同步的 Map。 這將使您的應(yīng)用程序容易崩潰(一種要確定和跟蹤的最糟糕的錯誤)。 但如果默認(rèn)為同步,則將因隨之而來的可怕性能而序列化執(zhí)行多線程應(yīng)用程序。 看起來,我們需要某種決策樹來幫助我們正確選擇。
Doug Lea 是紐約州立大學(xué)奧斯威戈分校計算機(jī)科學(xué)系的教授。 他創(chuàng)建了一組公共領(lǐng)域的程序包(統(tǒng)稱 util.concurrent),該程序包包含許多可以簡化高性能并行編程的實(shí)用程序類。 這些類中包含兩個 Map,即 ConcurrentReaderHashMap 和 ConcurrentHashMap。 這些 Map 實(shí)現(xiàn)是線程安全的,并且不需要對并發(fā)訪問或更新進(jìn)行同步,同時還適用于大多數(shù)需要 Map 的情況。 它們還遠(yuǎn)比同步的 Map(如 Hashtable)或使用同步的包裝器更具伸縮性,并且與 HashMap 相比,它們對性能的破壞很小。 util.concurrent 程序包構(gòu)成了 JSR166 的基礎(chǔ);JSR166 已經(jīng)開發(fā)了一個包含在 Java 1.5 版中的并發(fā)實(shí)用程序,而 Java 1.5 版將把這些 Map 包含在一個新的 java.util.concurrent 程序包中。
所有這一切意味著您不需要一個決策樹來決定是使用同步的 Map 還是使用非同步的 Map, 而只需使用 ConcurrentHashMap。 當(dāng)然,在某些情況下,使用 ConcurrentHashMap 并不合適。 但這些情況很少見,并且應(yīng)具體情況具體處理。 這就是監(jiān)測的用途。
結(jié)束語
通過 Oracle JDeveloper 可以非常輕松地創(chuàng)建一個用于比較各種 Map 性能的測試類。 更重要的是,集成良好的監(jiān)測器可以在開發(fā)過程中快速、輕松地識別性能瓶頸 - 集成到 IDE 中的監(jiān)測器通常被較頻繁地使用,以便幫助構(gòu)建一個成功的工程。 現(xiàn)在,您已經(jīng)擁有了一個監(jiān)測器并了解了有關(guān)通用 Map 及其性能的基礎(chǔ)知識,可以開始運(yùn)行您自己的測試,以查明您的應(yīng)用程序是否因 Map 而存在瓶頸以及在何處需要更改所使用的 Map。
以上內(nèi)容介紹了通用 Map 及其性能的基礎(chǔ)知識。 當(dāng)然,有關(guān)特定 Map 實(shí)現(xiàn)以及如何根據(jù)不同的需求使用它們還存在更多復(fù)雜和值得關(guān)注的事項,這些將在本文第 2 部分中介紹。
--------------------------------------------------------------------------------
Jack Shirazi 是 O''Reilly 的“Java 性能調(diào)整”的作者,以及受歡迎的 JavaPerformanceTuning.com 網(wǎng)站(提供 Java 性能信息的全球知名站點(diǎn))的總監(jiān)。 Jack 在 Java 性能領(lǐng)域提供咨詢并著書立說。 他還監(jiān)督 JavaPerformanceTuning.com 提供的信息,其中包括每年大約發(fā)布 1000 條性能技巧以及許多有關(guān)性能工具、討論組等內(nèi)容的文章。 Jack 早年還曾發(fā)布有關(guān)蛋白質(zhì)結(jié)構(gòu)預(yù)測以及黑洞熱力學(xué)方面的文章,而且在其空閑時還對某些 Perl5 核心模塊作出了貢獻(xiàn)。