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

享元模式深度解析:以「共享」換「空間」,大對象池優化實戰

     編輯:Java 程式設計入門

享元模式核心思想:將對象的內部狀態(不變、可共享)與外部狀態(變化、不可共享)分離,建立享元池複用內部狀態對象。適用於大量相似對象場景:文檔字符、地圖瓦片、數據庫連接池、線程池、字符串常量池。本文從結構、實現、XML 數據處理實戰三維度深度解析。

核心角色與結構

角色職責關鍵點
Flyweight (抽象享元)定義操作接口,接受外部狀態參數void operation(ExtrinsicState state)
ConcreteFlyweight (具體享元)實現接口,存儲內部狀態,必須可共享線程安全、無外部狀態依賴
UnsharedConcreteFlyweight (非共享享元)不進入池,每次新建,可攜帶外部狀態組合模式葉子節點常用此類
FlyweightFactory (享元工廠)維護享元池,getFlyweight(key) 獲取/創建池通常用 Map/ConcurrentHashMap
Client (客戶端)持有外部狀態,調用工廠獲取享元,調用操作負責計算/維護外部狀態

標準實現骨架

// 1. 外部狀態接口(自定義)
public interface ExtrinsicState { }

// 2. 抽象享元
public interface Flyweight {
    void operation(ExtrinsicState state);
}

// 3. 具體享元(可共享)
public class ConcreteFlyweight implements Flyweight {
    private final IntrinsicState intrinsicState; // 內部狀態,final 不變
    
    public ConcreteFlyweight(IntrinsicState intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    
    @Override
    public void operation(ExtrinsicState extrinsicState) {
        // 結合內部狀態 + 外部狀態執行業務邏輯
    }
}

// 4. 非共享享元
public class UnsharedConcreteFlyweight implements Flyweight {
    @Override
    public void operation(ExtrinsicState state) { /* 獨享邏輯 */ }
}

// 5. 享元工廠
public class FlyweightFactory {
    private final Map pool = new ConcurrentHashMap<>();
    
    public Flyweight getFlyweight(Object key) {
        return pool.computeIfAbsent(key, k -> new ConcreteFlyweight((IntrinsicState) k));
    }
    
    public int poolSize() { return pool.size(); }
}

客戶端調用模式

FlyweightFactory factory = new FlyweightFactory();

// 獲取共享享元(內部狀態由 key 決定)
Flyweight fly1 = factory.getFlyweight("Fred");  // 內部狀態="Fred"
Flyweight fly2 = factory.getFlyweight("Wilma"); // 內部狀態="Wilma"

// 客戶端維護外部狀態
ExtrinsicState context1 = new ExtrinsicStateImpl(/* 具體上下文 */);
ExtrinsicState context2 = new ExtrinsicStateImpl(/* 不同上下文 */);

// 調用操作:享元不保存外部狀態,每次傳入
fly1.operation(context1);
fly2.operation(context2);

實戰:XML 大數據去重優化

場景:解析包含大量重複歌手信息的 CD 目錄 XML,歌手名重複率高。

數據模型

<catalog>
  <cd>
    <year>2023</year>
    <artist>周杰倫</artist>  <!-- 高頻重複 -->
    <title>最偉大的作品</title>
  </cd>
  <cd>
    <year>2022</year>
    <artist>周杰倫</artist>  <!-- 共享享元候選 -->
    <title>說好不哭</title>
  </cd>
  ...
</catalog>

享元映射設計

字段類型享元角色
artist (歌手)高重複ConcreteFlyweight — 共享享元池
year (年份)低重複UnsharedConcreteFlyweight / 外部狀態
title (專輯)唯一外部狀態 / 非享元

實現代碼

// 內部狀態:歌手信息
public class Artist implements IntrinsicState {
    private final String name;
    private final String debutYear;
    private final String genre;
    
    public Artist(String name, String debutYear, String genre) {
        this.name = name;
        this.debutYear = debutYear;
        this.genre = genre;
    }
    // getters...
}

// 外部狀態:專輯上下文
public class AlbumContext implements ExtrinsicState {
    private final int year;
    private final String title;
    public AlbumContext(int year, String title) { ... }
}

// 享元工廠:解析時構建
public class ArtistFlyweightFactory {
    private final Map pool = new ConcurrentHashMap<>();
    
    public Artist getArtist(String name, String debutYear, String genre) {
        return pool.computeIfAbsent(name, 
            n -> new Artist(name, debutYear, genre));
    }
}

// 解析器片段
public class CdCatalogParser {
    private final ArtistFlyweightFactory artistFactory = new ArtistFlyweightFactory();
    
    public List parse(Document doc) {
        List records = new ArrayList<>();
        NodeList cdNodes = doc.getElementsByTagName("cd");
        
        for (int i = 0; i < cdNodes.getLength(); i++) {
            Element cd = (Element) cdNodes.item(i);
            String artistName = getText(cd, "artist");
            String debutYear = getText(cd, "debutYear"); // 假設 XML 有
            String genre = getText(cd, "genre");
            
            // 獲取/創建共享歌手享元
            Artist artist = artistFactory.getArtist(artistName, debutYear, genre);
            
            // 構建外部狀態
            AlbumContext ctx = new AlbumContext(
                Integer.parseInt(getText(cd, "year")),
                getText(cd, "title")
            );
            
            // 記錄引用享元 + 外部狀態
            records.add(new CdRecord(artist, ctx));
        }
        return records;
    }
}

// 最終記錄:輕量引用
public class CdRecord {
    private final Artist artist;        // 共享享元引用
    private final AlbumContext context; // 外部狀態
    // getters...
}

效果量化

指標無享元享元模式提升
10萬條記錄,1000位歌手10萬 Artist 對象1000 Artist 對象內存減 99%
GC 壓力高(大量短命對象)極低(池化長期存活)GC 停頓大幅下降
解析速度基準快 20-30%(少 new、少 GC)顯著提升

適用性判斷清單

  1. 應用使用大量相似對象
  2. 對象多數狀態可外部化
  3. 去除外部狀態後,可用較少共享對象替代
  4. 應用不依賴對象身份(== 比較),只依賴內容(equals())

避坑指南

  • 線程安全:池用 ConcurrentHashMap,享元類不可變
  • 外部狀態傳遞:不可存入享元,必須每次調用傳入
  • 池清理策略:長期運行需弱引用池或定期清理,防內存洩漏
  • 過度優化:對象數量少、生命週期短、狀態難分離時,直接 new 更簡單

享元模式是「以時間換空間」的經典權衡——用工廠查找/計算外部狀態的 CPU 時間,換取巨大的內存節省與 GC 壓力降低。在大數據、高併發、嵌入式場景,威力驚人。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved