你知道資料大小嗎?--不要花太多的功夫來隱藏類的成員(三) (轉)

gugu99發表於2008-01-27
你知道資料大小嗎?--不要花太多的功夫來隱藏類的成員(三) (轉)[@more@] 

我們能做點什麼呢?:namespace prefix = o ns = "urn:schemas--com::office" />

“這很好,但是我們沒有任何選擇除了使用String和其它提供的型別,是不是這樣呢?”我聽到你們再問,那麼讓我們來找找答案吧。

封裝類

封裝類比如java.lang.Integer,看起來儲存大量的資料在中像一個壞的選擇。如果你盡力為了記憶體的經濟,那麼就要避免這麼做。使用你自己的針對int的向量類並不難。當然,如果Java的核心庫已經包含了這個那就最好不過了。或許這種情況在Java擁有特殊型別的時候將會大大改觀。

多位陣列

對於使用多維陣列的大型的資料結構,你可以時常的透過簡單的變換減少額外的維數/例如:轉換int[dim1][dim2]的例項到一個int[dim1*dim2]的例項,改變所有形如a[i][j]的為a[i*dim1+j]。這樣你就不必花功夫在dim1上的索引檢查可以提高。

java.lang.String

你可以使用一些小技巧去減少你的應用中字串的靜態記憶體大小。

首先,你可以嘗試一種很平常的技術,就是當一個應用從一個資料或者連線中載入或者快取很多的字串,並且這種字串的值是有限變化的。舉個例子:如果你想分析一個XML檔案,在這個檔案中,你經常遇到某種屬性,但是這個屬性僅僅被限制在兩個可能的值。你的目標:透過一個雜湊對映過濾所有的字串,減少所有相同的但是明顯字串和目標引用一樣的。

  public String internString (String s)

  {

  if (s == null) return null;

 

  String is = (String) m_strings.get (s);

  if (is != null)

  return is;

  else

  {

  m_strings.put (s, s);

  return s;

  }

  }

 

  private Map m_strings = new HashMap ();

如果適用成功,這個技巧能夠成倍的減少你的靜態記憶體需求。一個富有的讀者應該能夠觀察到這個技巧複製java.lang.String.intern()的功能性。有無數的理由存在來讓你避免使用String.intern()方法。其中一個就是現在的JVM幾乎沒有能實現大量資料的保留。

如果你的字串是完全不同的,會發生什麼情況呢?這就是要介紹的第二個技巧,重新收集那些小的字串空間,這些空間潛在的隱藏於char陣列中,因為使用陣列大概只佔了字串封裝所佔用的記憶體的一半。因此,當我們的應用快取許多獨特的字元傳值,我們僅僅只要保持在記憶體中的陣列,在需要的時候轉換為字串。如果這個字串只是作為暫時的,很快就會拋棄,這將很有效果。一個簡單的實驗就是從一個字典檔案中選出作為快取的90000個單詞,這些資料大約5.6M的大小,如果是char的話,只需要3.4M的空間,只佔用了以前的65%。

第二個技巧明顯的包含一個不利條件,就是你不能支援透過一個建構函式轉換一個char[]成為字串,因為這個建構函式沒有複製這個陣列而將擁有這個陣列。為什麼呢?因為這個完全的public的字串確保每一個字串是不可變的,所以每個字串的建構函式顯然要複製輸入的資料然後傳入它的引數。

然後,我們將使用第三個技巧。這個技巧用在當轉換一個char陣列為一個字串的代價證實太高的時候。該技巧使用java.lang.String.substr()的功能避免資料複製:這個方法的是顯示用了字串的不變性,並且建立的一個影子字串物件來共享字元內容,但是它的內部的開始位置和結束位置都是正確的。我們還是寫一個例子,new String(“smiles”).substring(1,5)是一個字串,這個字串是字元緩衝從位置1到位置4的字元結束,並且這個字元緩衝將共享原來的字串建構函式指向的字元緩衝。你可以象一下這樣使用:給出一個大的字串集合,你可以合併它的字元內容到一個大的字元陣列,在它之上建立一個字串,並且使用這個主串的子串來重新建立一個原來的字串。如以下描述:

  public static String [] compactStrings (String [] strings)

  {

  String [] result = new String [strings.length];

  int offset = 0;

 

  for (int i = 0; i < strings.length; ++ i)

  offset += strings [i].length ();

 

  // Can't use StringBuffer due to how it manages capacity

  char [] allchars = new char [offset];

 

  offset = 0;

  for (int i = 0; i < strings.length; ++ i)

  {

  strings [i].getChars (0, strings [i].length (), allchars, offset);

  offset += strings [i].length ();

  }

 

  String allstrings = new String (allchars);

 

  offset = 0;

  for (int i = 0; i < strings.length; ++ i)

  result [i] = allstrings.substring (offset,

  offset += strings [i].length ());

 

  return result;

  }

以上方法返回一個新的字串集等同於輸入的字串集,但是在記憶體中更加得緊湊。重新獲得每個字串陣列的16個位元組的頭部,在方法中被有效的移除。這個在快取大多數短的字串時比較有效果。當這個方法用於同樣的90000個單詞的字典時,記憶體從5.6M節約到4.2M,大概節約了30%。

這些努力是否值得呢?

我這裡提到的方法看起來都是很細微的,是否值得花時間去實現呢?但是,記住我們腦子裡面應該記住:服務端的應用能夠快取大量的資料在記憶體中的話講能夠大大的提高從和提取資料的和效率。在當前32位的JVM中,幾百兆的快取資料代表堆中很引人注意的位置。減少30%或者更多不應該被嘲笑,它能將的可測性質中能提高很顯著的水平。當然這些技巧不適用於一開始就很好設計的資料結構,事實的決定要由hotspots來決定。無論如何,你現在應該更加了解你的物件消耗了多少記憶體。

關於作者:

Vladimir Roubtsov擁有超過12年的多種語言的經驗,其中包括從1995年就開始用得Java。目前,它作為資深開發者在Austin, Texas.為Trilogy開發企業級。平時的業餘愛好就是開發一些關於Java位元組程式碼或源程式程式碼的工具。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-998548/,如需轉載,請註明出處,否則將追究法律責任。

相關文章