定義:
使用共享物件疗疟,用來(lái)盡可能減少內(nèi)存使用量以及分享資訊給盡可能多的相似物件程帕;它適合用于只是因重復(fù)而導(dǎo)致使用無(wú)法令人接受的大量?jī)?nèi)存的大量物件。-
UML:
image.png- Flyweight:抽象享元角色,為具體享元角色規(guī)定了必須實(shí)現(xiàn)的方法地啰,而外蘊(yùn)狀態(tài)就是以參數(shù)的形式通過(guò)此方法傳入愁拭。
- ConcreteFlyweight:具體享元角色,實(shí)現(xiàn)抽象角色規(guī)定的方法。如果存在內(nèi)蘊(yùn)狀態(tài)(享元對(duì)象可共有部分)亏吝,就負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間岭埠。
- FlyweightFactory:享元工廠角色,負(fù)責(zé)創(chuàng)建和管理享元角色。
- Client: 客戶(hù)端角色,維護(hù)對(duì)所有享元對(duì)象的引用蔚鸥,而且還需要存儲(chǔ)對(duì)應(yīng)的外蘊(yùn)狀態(tài)(享元對(duì)象不可共有部分)惜论。
jdk中的享元模式:
String對(duì)象是我們最常用的享元模式,Java中將String類(lèi)定義為final(不可改變的),JVM中字符串一般保存在字符串常量池中止喷,這個(gè)字符串常量池在jdk 6.0以前是位于常量池中馆类,位于永久代,而在JDK 7.0中弹谁,JVM將其從永久代拿出來(lái)放置于堆中乾巧。
public static void main(String[] args){
String s = new String("abc");
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s==s1);
System.out.println(s1==s2);
System.out.println(s==s2);
System.out.println(s==s3);
}
執(zhí)行結(jié)果:
false
true
false
false
通過(guò)new 對(duì)象的方式創(chuàng)建的String不是享元的,通過(guò)字面量創(chuàng)建的是享元的僵闯。除此之外jdk中的基本類(lèi)型的自動(dòng)拆裝箱也使用了享元模式卧抗。
設(shè)計(jì)思想:
享元模式管理的細(xì)粒度對(duì)象有兩個(gè)狀態(tài)
內(nèi)蘊(yùn)狀態(tài)存儲(chǔ)在享元內(nèi)部,不會(huì)隨環(huán)境的改變而有所不同鳖粟,是可以共享的
外蘊(yùn)狀態(tài)是不可以共享的社裆,它隨環(huán)境的改變而改變的,因此外蘊(yùn)狀態(tài)是由客戶(hù)端來(lái)保持(因?yàn)榄h(huán)境的變化是由客戶(hù)端引起的)向图。模型:景觀設(shè)計(jì)圖泳秀。
景觀設(shè)計(jì)圖中,有大量的樹(shù)與草這樣的圖片榄攀,在使用它們的時(shí)候嗜傅,相同種類(lèi)的樹(shù)根草展示出來(lái)的效果圖都是一樣的,不同的是它們?cè)谠O(shè)計(jì)圖中的位置(x,y)坐標(biāo)不同檩赢。而這些不同坐標(biāo)的樹(shù)或草其實(shí)使用的都是一張?jiān)蛨D吕嘀,只要給定不同的坐標(biāo)就能組合出我們的景觀設(shè)計(jì)圖。
這里的原型圖就是享元模式中抽象出來(lái)的Flyweight贞瞒,也是內(nèi)蘊(yùn)狀態(tài)偶房,我們這里將樹(shù)與草抽象出來(lái)植物類(lèi):
public abstract class Plant {
public Plant() {}
//不同的是展示的位置
public abstract void display(int xCoord, int yCoord, int age);
}
具體享元草類(lèi):
public class Grass extends Plant {
@Override
public void display(int xCoord, int yCoord, int age) {
// System.out.print("Grass x");
}
}
具體享元樹(shù)類(lèi):
public class Tree extends Plant {
@Override
public void display(int xCoord, int yCoord, int age) {
// System.out.print("Tree x");
}
}
植物工廠,根據(jù)植物的類(lèi)型創(chuàng)建具體享元對(duì)象,這里我們只有2中類(lèi)型
public class PlantFactory {
private HashMap<Integer, Plant> plantMap = new HashMap<Integer, Plant>();
public PlantFactory() {}
public Plant getPlant(int type) {
if (!plantMap.containsKey(type)) {
switch (type) {
case 0:
plantMap.put(0, new Tree());
break;
case 1:
plantMap.put(1, new Grass());
break;
}
}
return plantMap.get(type);
}
}
享元對(duì)象的管理類(lèi),我們要?jiǎng)?chuàng)建一千萬(wàn)個(gè)植物军浆,他們有不同的位置:
public class PlantManager {
private int length = 10000000;
//維護(hù)享元的外蘊(yùn)狀態(tài)棕洋,坐標(biāo),年齡乒融,類(lèi)型
private int[] xArray = new int[length], yArray = new int[length],
AgeArray = new int[length], typeArray = new int[length];
private PlantFactory mPlantFactory;
public PlantManager() {
mPlantFactory = new PlantFactory();
for (int i = 0; i < length; i++) {
//生成享元對(duì)象的外蘊(yùn)屬性
xArray[i] = (int) (Math.random() * length);
yArray[i] = (int) (Math.random() * length);
AgeArray[i] = (int) (Math.random() * length) % 5;
typeArray[i] = (int) (Math.random() * length) % 2;
}
}
public void displayTrees() {
for (int i = 0; i < length; i++) {
//創(chuàng)建一個(gè)享元對(duì)象掰盘,并展示外蘊(yùn)屬性
mPlantFactory.getPlant(typeArray[i]).display(xArray[i], yArray[i], AgeArray[i]);
}
}
}
這里可以看出來(lái)摄悯,外蘊(yùn)屬性并不是享元對(duì)象維護(hù)的,而是由管理者維護(hù)愧捕。享元對(duì)象只需要維護(hù)自己的內(nèi)蘊(yùn)狀態(tài)奢驯,我們這里的草,樹(shù)沒(méi)有設(shè)置內(nèi)蘊(yùn)屬性晃财。
我們將Client寫(xiě)成測(cè)試類(lèi)叨橱,查看創(chuàng)建一千萬(wàn)個(gè)植物需要占用的內(nèi)存:
public class MainTest {
public static void main(String[] args) {
showMemInfo();
PlantManager mPlantManager;
mPlantManager = new PlantManager();
showMemInfo();
mPlantManager.displayTrees();
showMemInfo();
}
public static void showMemInfo() {
// 最大內(nèi)存:
long max = Runtime.getRuntime().maxMemory() / 1024 / 1024;
// 分配內(nèi)存:
long total = Runtime.getRuntime().totalMemory() / 1024 / 1024;
// 已分配內(nèi)存中的剩余空間 :
long free = Runtime.getRuntime().freeMemory() / 1024 /1024;
// 已占用的內(nèi)存:
long used = total - free;
System.out.println("最大內(nèi)存 = " + max + "M");
System.out.println("已分配內(nèi)存 = " + total + "M");
System.out.println("已分配內(nèi)存中的剩余空間 = " + free + "M");
System.out.println("已用內(nèi)存 = " + used + "M");
System.out.println("時(shí)間 = " + System.currentTimeMillis());
System.out.println("");
}
}
輸出:
最大內(nèi)存 = 1806M
已分配內(nèi)存 = 123M
已分配內(nèi)存中的剩余空間 = 120M
已用內(nèi)存 = 3M
時(shí)間 = 1517047082949
最大內(nèi)存 = 1806M
已分配內(nèi)存 = 200M
已分配內(nèi)存中的剩余空間 = 44M
已用內(nèi)存 = 156M
時(shí)間 = 1517047083852
最大內(nèi)存 = 1806M
已分配內(nèi)存 = 200M
已分配內(nèi)存中的剩余空間 = 44M
已用內(nèi)存 = 156M
時(shí)間 = 1517047083983
這里可以看到我們只使用到了77M內(nèi)存。如果不使用享元模式断盛,我們將樹(shù)與草設(shè)計(jì)成自己維護(hù)坐標(biāo)、年齡愉舔、類(lèi)型钢猛。那將會(huì)極大的消耗內(nèi)存,因?yàn)槊總€(gè)對(duì)象在堆中占用的空間至少大了12個(gè)字節(jié)轩缤,有興趣可以嘗試下命迈。