首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

享元模式深度解析:Java 中大量細粒度對象的記憶體優化藝術

     編輯:Java 相關知識

模式核心:共享技術支撐大量細粒度對象

享元模式通過區分內部狀態(Internal State)與外部狀態(External State),實現對象共享:

  • 內部狀態:不可變、可共享的數據(如字符編碼、圖標圖片)。
  • 外部狀態:隨環境變化、不可共享的數據(如字符顏色、位置、字體大小)。

五大核心角色

  1. Flyweight(抽象享元):定義介面,接收外部狀態參數。
  2. ConcreteFlyweight(具體享元):實現介面,儲存內部狀態,必須可共享。
  3. UnsharedConcreteFlyweight(非共享享元):不共享的組合對象,可包含具體享元。
  4. FlyweightFactory(享元工廠):創建/管理享元,確保合理共享(通常為單例)。
  5. Client(客戶端):維護享元引用,計算/儲存外部狀態。

Java 標準庫中的經典應用:String 常量池

String a = "hello";
String b = "hello";
System.out.println(a == b); // true:指向同一對象,享元模式實踐

JVM 確保字符串字面量在常量池中唯一,極大節省記憶體。

實戰場景:文本編輯器字符對象共享

問題:文章包含成千上萬字符,若每個字符都是獨立對象,記憶體壓力極大。
解決:

  • 字符對象(享元)僅儲存 char code(內部狀態)。
  • 顏色、字體、大小等格式屬性(外部狀態)由客戶端管理,渲染時傳入。
  • 行對象(非共享享元)組合字符享元,維護外部狀態上下文。

完整代碼實現

// 1. 享元對象:字母
public class Letter {
    private final char code; // 內部狀態:不可變
    public Letter(char c) { this.code = c; }
    public char getCode() { return code; }
    @Override public String toString() { return "Letter@" + Integer.toHexString(hashCode()) + "['" + code + "']"; }
}

// 2. 享元工廠(單例)
public class LetterFactory {
    private static final LetterFactory INSTANCE = new LetterFactory();
    private final Map pool = new ConcurrentHashMap<>();
    
    private LetterFactory() {}
    public static LetterFactory getInstance() { return INSTANCE; }
    
    public Letter getLetter(char c) {
        return pool.computeIfAbsent(c, Letter::new); // 雙重檢查鎖定模式隱含
    }
    
    public int poolSize() { return pool.size(); }
}

// 3. 非共享享元:文本行(組合字符享元)
public class TextLine {
    private final List letters = new ArrayList<>();
    private final Map styles = new HashMap<>(); // 外部狀態:位置->樣式
    
    public void addLetter(char c) { letters.add(LetterFactory.getInstance().getLetter(c)); }
    public void setStyle(int index, FontStyle style) { styles.put(index, style); }
    
    public void render(Graphics2D g, int x, int y) {
        for (int i = 0; i < letters.size(); i++) {
            FontStyle fs = styles.getOrDefault(i, FontStyle.DEFAULT);
            g.setFont(fs.font); g.setColor(fs.color);
            g.drawString(String.valueOf(letters.get(i).getCode()), x, y);
            x += fs.fontMetrics.charWidth(letters.get(i).getCode());
        }
    }
}

// 4. 客戶端測試
public class Test {
    public static void main(String[] args) {
        LetterFactory factory = LetterFactory.getInstance();
        String text = "easiness";
        
        // 模擬構建文本行
        TextLine line = new TextLine();
        for (char c : text.toCharArray()) line.addLetter(c);
        
        // 設置部分字符樣式(外部狀態)
        line.setStyle(0, new FontStyle(new Font("微軟雅黑", Font.BOLD, 24), Color.RED));
        line.setStyle(3, new FontStyle(new Font("Consolas", Font.ITALIC, 18), Color.BLUE));
        
        System.out.println("享元池大小: " + factory.poolSize()); // 輸出 5 (e,a,s,i,n)
        System.out.println("字符對象: " + line.letters); // 顯示共享對象引用
    }
}

關鍵輸出分析

享元池大小: 5
字符對象: [Letter@...['e'], Letter@...['a'], Letter@...['s'], Letter@...['i'], Letter@...['n'], Letter@...['e'], Letter@...['s'], Letter@...['s']]

8 個字符位置,僅創建 5 個字符享元對象,e、s 重複複用。

優缺點與適用場景

優點

  • 大幅減少對象數量,降低記憶體佔用、GC 壓力。
  • 內部狀態不可變,天然線程安全。

缺點

  • 增加系統複雜度,需嚴格區分內外部狀態。
  • 讀取外部狀態會略微增加運行時開銷。

適用場景

  • 系統中存在大量相似/相同對象(如字符、圖標、棋子、坐標點)。
  • 對象大部分狀態可外部化,且外部狀態相對固定。
  • 記憶體敏感環境(嵌入式、大數據緩存、遊戲開發)。

避坑指南

  • 不可變性是前提:內部狀態必須不可變,否則共享會導致數據錯亂。
  • 工廠需線程安全:高併發下使用 ConcurrentHashMap 或雙重檢查鎖定。
  • 不要過度使用:對象數量少、狀態差異大時,享元模式得不償失。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved