前言
享元模式是非常常用的一種結構性設計模式,
特別是在面試的時候,當我們把這一節內容掌握,我相信不管是作業中還是面試中這一塊內容絕對是一大亮點,
什么是享元模式
所謂“享元”,顧名思義就是被共享的單元,享元模式的意圖是復用物件,節省記憶體,前提是享元物件是不可變物件,
具體來講,當一個系統中存在大量重復物件的時候,如果這些重復的物件是不可變物件,我們就可以利用享元模式將物件設計成享元,在記憶體中只保留一份實體,供多處代碼參考,這樣可以減少記憶體中物件的數量,起到節省記憶體的目的,
這里值得注意的是只保留一份實體,供多人使用,
面試最常見的面試題
我相信大伙在面試的時候經常會被問到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=<size>} 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 =
https://www.cnblogs.com/zhxiansheng/p/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 類中要共享的物件,是在類加載的時候,就集中一次性創建好的,
但是,對于字串來說,我們沒法事先知道要共享哪些字串常量,所以沒辦法事先創建好,
只能在某個字串常量第一次被用到的時候,存盤到常量池中,當之后再用到的時候,直接參考常量池中已經存在的即可,就不需要再重新創建了
實際運用
我們想想,什么情況我們應該使用享元模式,
我總結了一下:
- 首先這個物件在很多地方都得使用,否則就是過度設計,
- 其次這個物件是不可變的,可以讓多個執行緒同時使用,
我舉一個具體的例子,
比如我們開發一個麻將游戲,沒一局游戲是不是要new一個麻將桌,new一副麻將,假如同時在線100w人,那我們就new了25w個麻將桌和25w副麻,
我們仔細想想能不能用享元模式來優化,首先麻將桌應該是不能優化的,因為他得記錄我們每一局游戲得狀態,桌上麻將的情況,等等資訊,但是麻將我們卻可以快取一副,讓他不可變,所有人共用這一副快取的麻將,
總結
享元模式其實開發中我們用的不是特別多,但是當需要時,卻非常的有效,包括面試中關于String,基本型別的包裝類關于享元模式的運用,當面試管再拋出這個問題,如果你能回答清楚并且提出其設計模式是享元模式,我相信一定會讓面試官眼前一亮,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/19614.html
標籤:設計模式
上一篇:UML
下一篇:GoF的23種設計模式分類和功能
