1. 程式人生 > >結構型 --面試高頻之享元模式

結構型 --面試高頻之享元模式

# 前言 享元模式是非常常用的一種結構性設計模式。 特別是在面試的時候。當我們把這一節內容掌握,我相信不管是工作中還是面試中這一塊內容絕對是一大亮點。 # 什麼是享元模式 所謂“享元”,顧名思義就是被共享的單元。享元模式的意圖是複用物件,節省記憶體,前提是享元物件是不可變物件。 具體來講,當一個系統中存在大量重複物件的時候,如果這些重複的物件是不可變物件,我們就可以利用享元模式將物件設計成享元,在記憶體中只保留一份例項,供多處程式碼引用。這樣可以減少記憶體中物件的數量,起到節省記憶體的目的。 這裡值得注意的是只保留一份例項,供多人使用。 # 面試最常見的面試題 我相信大夥在面試的時候經常會被問到String,Integer相關的面試題。 那我們就從這兩塊內容開始講解。 ## 享元模式在Integer中的應用 我們先來看下面這樣一段程式碼。 ``` Integer i1 = 56; Integer i2 = 56; Integer i3 = 129; Integer i4 = 129; System.out.println(i1 == i2); //true System.out.println(i3 == i4); //false ``` 我相信很多人在面試的時候會遇到這種題目。答案可能會出乎我們的意料。第一個為true,第二個為false。 這正是因為 Integer,用到了享元模式來複用物件,才導致了這樣的執行結果。當我們通過自動裝箱,也就是呼叫 valueOf() 來建立 Integer 物件的時候,如果要建立的 Integer 物件的值在 -128 到 127 之間,會從 IntegerCache 類中直接返回,否則才呼叫 new 方法建立。看程式碼更加清晰一些,Integer 類的 valueOf() 函式的具體程式碼如下所示: ``` //從這裡的原始碼我們能看到,當我們執行Integer i2 = 56; //這行程式碼的時候。其實是通過自動裝箱機制,呼叫的valueOf。 //當資料在IntegerCache.low~IntegerCache.high之間的時候,我們是直接從快取中拿取的資料。 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } ``` 那這個IntegerCache是什麼呢?這個其實是Integer的內部類。 我們挑選重點程式碼來看看,原始碼如下: ``` /** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; //快取的最小值 static final int high; //快取的最大值 static final Integer cache[]; //快取 static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >
= 127; } private IntegerCache() {} } ``` 這個是Integer的靜態內部類,當我們載入Ineger的時候該類也會被載入進去。可以看到他快取了-128 到 127 之間的整型值。 實際上,除了 Integer 型別之外,其他包裝器型別,比如 Long、Short、Byte 等,也都利用了享元模式來快取 -128 到 127 之間的資料。比如,Long 型別對應的 LongCache 享元工廠類及 valueOf() 。 其實jdk考慮的很周到,我們大部分時間創建出來的Ineger物件,其實都是儲存整型都不是特別大。所以乾脆取一段大小合理的資料直接快取下來。 舉一個極端一點的例子,假設程式需要建立 1 萬個 -128 到 127 之間的 Integer 物件。使用第一種建立方式,我們需要分配 1 萬個 Integer 物件的記憶體空間;使用後兩種建立方式,我們最多隻需要分配 256 個 Integer 物件的記憶體空間。 ## 享元模式在String中的應用 我們都知道String是被final修飾的,大家又仔細想過這其中的緣由嗎? 這最大的原因就是為了實現字串池化技術。其核心思想就是享元模式。 我們前面提到過享元物件都是不可變的。這樣我們才能保證大家在共同使用的時候不會出現問題。所以String是被final修飾的。 我們再來看一下這段程式碼: ``` String s1 = "享元模式"; String s2 = "享元模式"; String s3 = new String("享元模式"); System.out.println(s1 == s2); //ture System.out.println(s1 == s3); //false ``` 前兩個s1和s2都是指向的字串常量池的"享元模式"。而s3指向的是堆的String。 String 類的享元模式的設計,跟 Integer 類稍微有些不同。 Integer 類中要共享的物件,是在類載入的時候,就集中一次性建立好的。 但是,對於字串來說,我們沒法事先知道要共享哪些字串常量,所以沒辦法事先建立好。 只能在某個字串常量第一次被用到的時候,儲存到常量池中,當之後再用到的時候,直接引用常量池中已經存在的即可,就不需要再重新建立了 # 實際運用 我們想想,什麼情況我們應該使用享元模式。 我總結了一下: 1. 首先這個物件在很多地方都得使用,否則就是過度設計。 2. 其次這個物件是不可變的,可以讓多個執行緒同時使用。 我舉一個具體的例子。 比如我們開發一個麻將遊戲。沒一局遊戲是不是要new一個麻將桌,new一副麻將。假如同時線上100w人,那我們就new了25w個麻將桌和25w副麻。 我們仔細想想能不能用享元模式來優化,首先麻將桌應該是不能優化的,因為他得記錄我們每一局遊戲得狀態,桌上麻將的情況,等等資訊。但是麻將我們卻可以快取一副,讓他不可變。所有人共用這一副快取的麻將。 # 總結 享元模式其實開發中我們用的不是特別多,但是當需要時,卻非常的有效。包括面試中關於String,基本型別的包裝類關於享元模式的運用。當面試管再丟擲這個問題,如果你能回答清楚並且提出其設計模式是享元模式,我相信一定會讓面試官眼前