1 概述
享元模式(flyweight Pattern)是通過重用元素來降低內存開銷的一種設計模式。
2 享元模式
所謂享元,意思是共享元素。當程序需要創建大量元素,或創建一些佔用大量內存的元素時,對服務器的內存資源是很大的挑戰。這時可以應用享元模式,將元素拆分成變量與不變量兩部分。其中不變量,是所有的元素共通的部分,可以共享;變量,可以做爲不同的元素的區分。比如要渲染一片森林,我們不需要爲每一顆樹都新建一個對象,因爲每一顆“樹”的渲染方式都是一樣的,不同的只是“座標“而已。這裏樹對象就是不變量,樹座標是變量,這樣可以極大地減少內存開銷。
3 案例
享元模式在JDK
中有廣泛應用。看下面一個例子:
public class Test {
public static void main(String[] args) {
// 直接賦值的String對象會被放到常量池中
String a = "123";
String b = "123";
System.out.println("is String instance equal: " + (a == b));
}
}
輸出:
is String instance equal: true
兩個對象之所以相等,是因爲直接賦值的String
類型變量,默認會被放到JVM
的String Pool
裏面。如果兩個String
變量的字面量一樣,那它們指向的就是String Pool
裏的同一個對象。
通過對String
對象池化的處理,可以複用對象,降低內存的開銷。這是享元模式的應用。
再來看一個簡單的例子:
public class Test {
public static void main(String[] args) {
// [-128, 127] 之間的值,會放入JVM的緩存之中
Integer i1 = Integer.valueOf(127);
Integer i2 = Integer.valueOf(127);
Integer i3 = Integer.valueOf(128);
Integer i4 = Integer.valueOf(128);
System.out.println("is instance 127 equal: " + (i1 == i2));
System.out.println("is instance 128 equal: " + (i3 == i4));
}
}
輸出:
is instance 127 equal: true
is instance 128 equal: false
上面的輸出看似很奇怪,當我們進入valueOf()
方法去看,便知道原因了:
public final class Integer extends Number implements Comparable<Integer> {
// 如果是[IntegerCache.low, IntegerCache.high](默認是[-128, 127])之間的值,直接從緩存中取
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// 內部類,用來放Integer的緩存
private static class IntegerCache {
static final int low = -128;
static final int high;
// 緩存數組
static final Integer cache[];
// 靜態塊,初始化緩存數組
static {
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() {}
}
}
默認情況下,[-127, 128]
之間的值,會被放入緩存中,因爲設計者認爲這個區間的值的使用率相對來說會更高。其實不僅僅是Integer
,其他的包裝類如Long
, Short
的valueOf()
方法,也都有做緩存的處理。這也是享元模式的應用。所以新建包裝類的時候,我們應該首先用valueOf()
方法,而不是直接new
。
享元模式通常會跟工廠模式一起使用,因爲享元模式本質上是控制過量對象的創建,而工廠模式正是常用的創建型模式,可以將共享對象創建的邏輯放在工廠類中。在上例中,Integer
實際上就充當了一個工廠類,而valueOf()
方法是類創建的入口。
4 總結
當需要創建大量對象,而對象之間又有很多共通之處時,可以考慮使用享元模式。而如果對象之間差異較大,引入享元模式反而會增加系統的複雜度。