相信大家日常開發(fā)過程中,一個優(yōu)秀的程序猿寫出的代碼一定要節(jié)省空間的,比如節(jié)省內(nèi)存,節(jié)省磁盤等等。那么如何通過設(shè)計模式來節(jié)省內(nèi)存呢?
1、什么是享元模式?
Use sharing to support large numbers of fine-grained objects efficiently.
享元模式(Flyweight Pattern):使用共享對象可有效地支持大量的細粒度的對象。
說人話:復(fù)用對象,節(jié)省內(nèi)存。
2、享元模式定義
①、Flyweight——抽象享元角色
是一個產(chǎn)品的抽象類, 同時定義出對象的外部狀態(tài)和內(nèi)部狀態(tài)的接口或?qū)崿F(xiàn)。
一個對象信息可以分為內(nèi)部狀態(tài)和外部狀態(tài)。
內(nèi)部狀態(tài) :對象可共享出來的信息, 存儲在享元對象內(nèi)部并且不會隨環(huán)境改變而改變,可以作為一個對象的動態(tài)附加信息, 不必直接儲存在具體某個對象中, 屬于可以共享的部分。
外部狀態(tài) :對象得以依賴的一個標(biāo)記, 是隨環(huán)境改變而改變的、 不可以共享的狀態(tài)。
②、ConcreteFlyweight——具體享元角色
具體的一個產(chǎn)品類, 實現(xiàn)抽象角色定義的業(yè)務(wù)。該角色中需要注意的是內(nèi)部狀態(tài)處理應(yīng)該與環(huán)境無關(guān), 不應(yīng)該出現(xiàn)一個操作改變了內(nèi)部狀態(tài), 同時修改了外部狀態(tài), 這是絕對不允許的。
③、unsharedConcreteFlyweight——不可共享的享元角色
不存在外部狀態(tài)或者安全要求(如線程安全) 不能夠使用共享技術(shù)的對象, 該對象一般不會出現(xiàn)在享元工廠中。
職責(zé)非常簡單, 就是構(gòu)造一個池容器, 同時提供從池中獲得對象的方法。
3、享元模式通用代碼
/**
* 抽象享元角色
*/
public abstract class Flyweight {
// 內(nèi)部狀態(tài)
private String instrinsic;
// 外部狀態(tài) 通過 final 修改,防止修改
protected final String extrinsic;
protected Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}
// 定義業(yè)務(wù)操作
public abstract void operate();
public String getInstrinsic() {
return instrinsic;
}
public void setInstrinsic(String instrinsic) {
this.instrinsic = instrinsic;
}
}
/**
* 具體享元角色1
*/
public class ConcreteFlyweight1 extends Flyweight{
protected ConcreteFlyweight1(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
System.out.println("具體享元角色1");
}
}
/**
* 具體享元角色2
*/
public class ConcreteFlyweight2 extends Flyweight{
protected ConcreteFlyweight2(String extrinsic) {
super(extrinsic);
}
@Override
public void operate() {
System.out.println("具體享元角色2");
}
}
public class FlyweightFactory {
// 定義一個池容器
private static HashMap< String,Flyweight > pool = new HashMap< >();
// 享元工廠
public static Flyweight getFlyweight(String extrinsic){
// 需要返回的對象
Flyweight flyweight = null;
// 池中沒有該對象
if(pool.containsKey(extrinsic)){
flyweight = pool.get(extrinsic);
}else{
// 根據(jù)外部狀態(tài)創(chuàng)建享元對象
flyweight = new ConcreteFlyweight1(extrinsic);
// 放置到池中
pool.put(extrinsic,flyweight);
}
return flyweight;
}
}
4、通過享元設(shè)計文本編輯器
假設(shè)文本編輯器只包含文字編輯功能,而且只記錄文字和格式兩部分信息,其中格式包括文字的字體型號、大小、顏色等信息。
4.1 普通實現(xiàn)
通常設(shè)計是把每個文字看成一個單獨對象。
package com.itcoke.designpattern.flyweight.edittext;
/**
* 單個文字對象
*/
public class Character {
// 字符
private char c;
// 字體型號
private String font;
// 字體大小
private int size;
// 字體顏色
private int colorRGB;
public Character(char c, String font, int size, int colorRGB){
this.c = c;
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
@Override
public String toString() {
return String.valueOf(c);
}
}
/**
* 編輯器實現(xiàn)
*/
public class Editor {
private ArrayList< Character > chars = new ArrayList< >();
public void appendCharacter(char c, String font, int size, int colorRGB){
Character character = new Character(c,font,size,colorRGB);
chars.add(character);
}
public void display(){
System.out.println(chars);
}
}
客戶端:
public class EditorClient {
public static void main(String[] args) {
Editor editor = new Editor();
editor.appendCharacter('A',"宋體",11,0XFFB6C1);
editor.appendCharacter('B',"宋體",11,0XFFB6C1);
editor.appendCharacter('C',"宋體",11,0XFFB6C1);
editor.display();
}
}
4.2 享元模式改寫
上面的問題很容易發(fā)現(xiàn),每一個字符就會創(chuàng)建一個 Character 對象,如果是幾百萬個字符,那內(nèi)存中就會存在幾百萬的對象,那怎么去節(jié)省這些內(nèi)存呢?
其實,分析一下,對于字體的格式,通常不會有很多,于是我們可以把字體格式設(shè)置為享元,也就是上面說的可以共享的內(nèi)部狀態(tài)。
內(nèi)部狀態(tài)(共享):字體類型、大小、顏色
外部狀態(tài)(不共享):字符
于是代碼改寫如下:
public class CharacterStyle {
// 字體型號
private String font;
// 字體大小
private int size;
// 字體顏色
private int colorRGB;
public CharacterStyle(String font, int size, int colorRGB) {
this.font = font;
this.size = size;
this.colorRGB = colorRGB;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CharacterStyle that = (CharacterStyle) o;
return size == that.size &&
colorRGB == that.colorRGB &&
Objects.equals(font, that.font);
}
@Override
public int hashCode() {
return Objects.hash(font, size, colorRGB);
}
}
public class CharacterStyleFactory {
private static final Map< CharacterStyle,CharacterStyle > mapStyles = new HashMap< >();
public static CharacterStyle getStyle(String font, int size, int colorRGB){
CharacterStyle newStyle = new CharacterStyle(font,size,colorRGB);
if(mapStyles.containsKey(newStyle)){
return mapStyles.get(newStyle);
}
mapStyles.put(newStyle,newStyle);
return newStyle;
}
}
public class Character {
private char c;
private CharacterStyle style;
public Character(char c, CharacterStyle style) {
this.c = c;
this.style = style;
}
@Override
public String toString() {
return String.valueOf(c);
}
}
public class Editor {
private List< Character > chars = new ArrayList< >();
public void appendCharacter(char c, String font, int size, int colorRGB){
Character character = new Character(c,CharacterStyleFactory.getStyle(font,size,colorRGB));
chars.add(character);
}
public void display(){
System.out.println(chars);
}
}
5、享元模式在 java.lang.Integer 中應(yīng)用
看下面這段代碼,打印結(jié)果是啥?
public class IntegerTest {
public static void main(String[] args) {
Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}
為什么是這種結(jié)果呢?
首先說一下 Integer i = 59;底層執(zhí)行了:Integer i = Integer.valueOf(59); 這是自動裝箱。
int j = i; 底層執(zhí)行了:int j = i.intValue(); 這是自動拆箱。
然后我們Integer.valueOf() 方法:
再看 IntegerCache 源碼:
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() {}
}
其實這就是我們前面說的享元對象的工廠類,緩存 -128 到 127 之間的整型值,這是最常用的一部分整型值,當(dāng)然JDK 也提供了方法來讓我們可以自定義緩存的最大值。
6、享元模式優(yōu)點
減少應(yīng)用程序創(chuàng)建的對象, 降低程序內(nèi)存的占用, 增強程序的性能。
但它同時也提高了系統(tǒng)復(fù)雜性, 需要分離出外部狀態(tài)和內(nèi)部狀態(tài), 而且外部狀態(tài)具有固化特性, 不應(yīng)該隨內(nèi)部狀態(tài)改變而改變, 否則導(dǎo)致系統(tǒng)的邏輯混亂。
7、享元模式應(yīng)用場景
①、系統(tǒng)中存在大量的相似對象。
②、細粒度的對象都具備較接近的外部狀態(tài), 而且內(nèi)部狀態(tài)與環(huán)境無關(guān), 也就是說對象沒有特定身份。
③、需要緩沖池的場景。
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
2999瀏覽量
73882 -
代碼
+關(guān)注
關(guān)注
30文章
4747瀏覽量
68348 -
設(shè)計模式
+關(guān)注
關(guān)注
0文章
53瀏覽量
8622 -
線程
+關(guān)注
關(guān)注
0文章
504瀏覽量
19651
發(fā)布評論請先 登錄
相關(guān)推薦
評論