設計模式,結構模式之享元模式

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類型變量,默認會被放到JVMString 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, ShortvalueOf()方法,也都有做緩存的處理。這也是享元模式的應用。所以新建包裝類的時候,我們應該首先用valueOf()方法,而不是直接new

享元模式通常會跟工廠模式一起使用,因爲享元模式本質上是控制過量對象的創建,而工廠模式正是常用的創建型模式,可以將共享對象創建的邏輯放在工廠類中。在上例中,Integer實際上就充當了一個工廠類,而valueOf()方法是類創建的入口。

4 總結

當需要創建大量對象,而對象之間又有很多共通之處時,可以考慮使用享元模式。而如果對象之間差異較大,引入享元模式反而會增加系統的複雜度。

文中例子的github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章