運用共享技術有效的支持大量細粒度的對象java
假設開發了一款簡單的遊戲:玩家們在地圖上移動並進行相互射擊。大量的子彈、導彈和爆炸彈片在整個地圖上穿行,爲玩家提供緊張刺激的遊戲體驗。可是,運行了幾分鐘後,遊戲由於內存容量不足而發生了崩潰。研究發現,每一個粒子(一顆子彈、 一枚導彈或一塊彈片)都由包含完整數據的獨立對象來表示。當玩家在遊戲中鏖戰進入高潮後的某一時刻,遊戲將沒法在剩餘內存中載入新建粒子,因而程序就崩潰了緩存
仔細觀察粒子Particle類,會注意到顏色(color)和精靈圖(sprite)這兩個成員變量所消耗的內存要比其餘變量多得多。另外,對全部例子來講,這兩個成員變量所存儲的數據幾乎徹底同樣 (好比全部子彈的顏色和精靈圖都同樣)。每一個粒子的另外一些狀態 (座標、 移動矢量和速度)則是不一樣的,由於這些成員變量的數值會不斷變化。內在狀態存儲於flyweight中,它包含了獨立於場景的信息,這些信息使得flyweight能夠被共享。而外部狀態取決於flyweight場景,並根據場景而變化,所以不可共享。用戶對象負責在必要的時候將外部狀態傳遞給Flyweightdom
使用FlyWeight模式時,傳輸、查找和/或計算外部狀態都會產生運行時開銷,尤爲當flyweight原先被存儲爲內部狀態時。然而,空間上的節省抵消了這些開銷。共享的flyweight越多,空間節省越大ide
存儲節約由如下因素決定:this
1. 因爲共享帶來的實例總數減小的數量spa
2. 對象內部狀態的平均數量rest
3. 外部狀態是計算的仍是存儲的code
共享的flyweight越多,存儲節約的也就越多。節約量隨着共享狀態的增多而增大。當對象使用大量的內部及外部狀態,而且外部狀態是計算出來的而非存儲的時候,節約量將達到最大對象
若是渲染一片森林 (1,000,000 棵樹)!每棵樹都由包含一些狀態的對象來表示 (座標和紋理等)。 儘管程序可以完成其主要工做,但很顯然它須要消耗大量內存。由於,太多樹對象包含重複數據 (名稱、 紋理和顏色)。所以咱們可用享元模式來將這些數值存儲在單獨的享元對象中 (TreeType類)。如今咱們再也不將相同數據存儲在數千個 Tree對象中,而是使用一組特殊的數值來引用其中一個享元對象。客戶端代碼不會知道任何事情, 由於重用享元對象的複雜機制隱藏在了享元工廠中blog
trees/Tree.java: 包含每棵樹的獨特狀態
package flyweight.trees; import java.awt.*; /** * @author GaoMing * @date 2021/7/19 - 22:14 */ public class Tree { private int x; private int y; private TreeType type; public Tree(int x, int y, TreeType type) { this.x = x; this.y = y; this.type = type; } public void draw(Graphics g) { type.draw(g, x, y); } }
trees/TreeType.java: 包含多棵樹共享的狀態
package flyweight.trees; import java.awt.*; /** * @author GaoMing * @date 2021/7/19 - 22:14 */ public class TreeType { private String name; private Color color; private String otherTreeData; public TreeType(String name, Color color, String otherTreeData) { this.name = name; this.color = color; this.otherTreeData = otherTreeData; } public void draw(Graphics g, int x, int y) { g.setColor(Color.BLACK); g.fillRect(x - 1, y, 3, 5); g.setColor(color); g.fillOval(x - 5, y - 10, 10, 10); } }
trees/TreeFactory.java: 封裝建立享元的複雜機制
package flyweight.trees; import java.awt.*; import java.util.HashMap; import java.util.Map; /** * @author GaoMing * @date 2021/7/19 - 22:15 */ public class TreeFactory { static Map<String, TreeType> treeTypes = new HashMap<>(); public static TreeType getTreeType(String name, Color color, String otherTreeData) { TreeType result = treeTypes.get(name); if (result == null) { result = new TreeType(name, color, otherTreeData); treeTypes.put(name, result); } return result; } }
forest/Forest.java: 咱們繪製的森林
package flyweight.forest; import flyweight.trees.TreeFactory; import flyweight.trees.TreeType; import flyweight.trees.Tree; import javax.swing.*; import java.awt.*; import java.util.ArrayList; /** * @author GaoMing * @date 2021/7/19 - 22:16 */ public class Forest extends JFrame { private List<Tree> trees = new ArrayList<>(); public void plantTree(int x, int y, String name, Color color, String otherTreeData) { TreeType type = TreeFactory.getTreeType(name, color, otherTreeData); Tree tree = new Tree(x, y, type); trees.add(tree); } @Override public void paint(Graphics graphics) { for (Tree tree : trees) { tree.draw(graphics); } } }
Demo.java: 客戶端代碼
package flyweight; import java.awt.*; import flyweight.forest.Forest; /** * @author GaoMing * @date 2021/7/19 - 22:17 */ public class Demo { static int CANVAS_SIZE = 500; static int TREES_TO_DRAW = 1000000; static int TREE_TYPES = 2; public static void main(String[] args) { Forest forest = new Forest(); for (int i = 0; i < Math.floor(TREES_TO_DRAW / TREE_TYPES); i++) { forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE), "Summer Oak", Color.GREEN, "Oak texture stub"); forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE), "Autumn Oak", Color.ORANGE, "Autumn Oak texture stub"); } forest.setSize(CANVAS_SIZE, CANVAS_SIZE); forest.setVisible(true); System.out.println(TREES_TO_DRAW + " trees drawn"); System.out.println("---------------------"); System.out.println("Memory usage:"); System.out.println("Tree size (8 bytes) * " + TREES_TO_DRAW); System.out.println("+ TreeTypes size (~30 bytes) * " + TREE_TYPES + ""); System.out.println("---------------------"); System.out.println("Total: " + ((TREES_TO_DRAW * 8 + TREE_TYPES * 30) / 1024 / 1024) + "MB (instead of " + ((TREES_TO_DRAW * 38) / 1024 / 1024) + "MB)"); } private static int random(int min, int max) { return min + (int) (Math.random() * ((max - min) + 1)); } }
運行結果
1000000 trees drawn --------------------- Memory usage: Tree size (8 bytes) * 1000000 + TreeTypes size (~30 bytes) * 2 --------------------- Total: 7MB (instead of 36MB)
1. 只會有一個單例實體,可是享元類能夠有多個實體,各實體的內在狀態也能夠不一樣
2. 單例對象能夠是可變的。享元對象是不可變的
java.lang.Integer#valueOf(int) (以及 Boolean、Byte、Character、Short、Long 和 BigDecimal)
識別方法:享元能夠經過構建方法來識別,它會返回緩存對象而不是建立新的對象