Skip to main content

flyweight

使用共享来有效支持大量细粒度的对象

Source facts

Repository
microwind/ai-skills
Last source activity
March 31, 2026 at 01:27
Detected SKILL.md language
Chinese
Stars
83
Forks
24

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
4 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
Flyweight
description
使用共享来有效支持大量细粒度的对象
license
MIT
# Flyweight Pattern (享元模式) ## 核心概念 **Flyweight** 是一种**结构型设计模式**,通过共享细粒度对象来节省内存。 ### 核心思想 - 💾 **内存优化** - 消除重复对象 - ⚡ **性能提升** - 减少GC压力、减少创建开销 - 🔄 **状态分离** - 内部状态(共享) vs 外部状态(不共享) - 🎯 **对象池** - 复用而非频繁创建销毁 ### 适用条件 - 对象数量很大(数百万级) - 创建对象成本高 - 对象之间有大量重复状态 - 可以接受共享 ## 何时使用 1. **大量细粒度对象** - 文本编辑器中的字符、游戏中的粒子、图形系统中的图元 2. **内存是瓶颈** - 对象数量多导致GC频繁 3. **对象有可复用的内容** - 如"字体"对象可被共享 4. **需要高效缓存** - 对象池的实现 5. **支持数据库连接池、线程池** - 连接/线程可复用 ## 实现方式 ### 方法1: 经典对象池 ```java // 享元对象 - immutable public class Character { private char value; private String font; private int size; public Character(char v, String f, int s) { this.value = v; this.font = f; this.size = s; } public void display(int x, int y) { // 渲染 } } // 享元工厂 - 管理对象池 public class CharacterFactory { private Map<String, Character> pool = new HashMap<>(); public Character getCharacter(char c, String font, int size) { String key = c + "-" + font + "-" + size; return pool.computeIfAbsent(key, k -> new Character(c, font, size) ); } public int poolSize() { return pool.size(); } } // 使用 CharacterFactory factory = new CharacterFactory(); Character a1 = factory.getCharacter('A', "Arial", 12); Character a2 = factory.getCharacter('A', "Arial", 12); assert a1 == a2; // 同一对象! ``` ### 方法2: 分类对象池 ```java public class CategorizedFlyweightFactory { private Map<Class<?>, Map<String, Object>> pools = new HashMap<>(); public <T> T get(Class<T> type, String key, Supplier<T> supplier) { Map<String, Object> pool = pools.computeIfAbsent( type, k -> new ConcurrentHashMap<>() ); return type.cast(pool.computeIfAbsent(key, k -> supplier.get())); } } // 使用 CategorizedFlyweightFactory factory = new CategorizedFlyweightFactory(); Font arial12 = factory.get(Font.class, "Arial-12", () -> new Font("Arial", 12) ); ``` ### 方法3: 弱引用池(自动清理) ```java public class WeakReferenceFlyweightFactory { private Map<String, WeakReference<Flyweight>> pool = new WeakHashMap<>(); public Flyweight get(String key, Supplier<Flyweight> supplier) { WeakReference<Flyweight> ref = pool.get(key); Flyweight obj = ref != null ? ref.get() : null; if (obj == null) { obj = supplier.get(); pool.put(key, new WeakReference<>(obj)); } return obj; } } ``` ### 方法4: LRU缓存池 ```java public class LRUFlyweightFactory { private final int maxSize; private LinkedHashMap<String, Flyweight> cache; public LRUFlyweightFactory(int size) { this.maxSize = size; this.cache = new LinkedHashMap<String, Flyweight>(size, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxSize; } }; } public Flyweight get(String key, Supplier<Flyweight> supplier) { return cache.computeIfAbsent(key, k -> supplier.get()); } } ``` ## 深入讲解:内部状态 vs 外部状态 ### 核心概念澄清 **内部状态(Intrinsic State)**: - 📌 **不可变** (immutable) - 🔄 **可共享** (shareable) - 📦 **体积小** (compact) - 例:Font对象、Texture对象、颜色配置 **外部状态(Extrinsic State)**: - 🔀 **易变** (mutable) - 🚫 **不可共享** (unshareable) - 📐 **通常是坐标、位置、临时数据** - 例:屏幕坐标(x, y)、对象ID、操作参数 ### 判断标准 - 状态分离矩阵 ``` 属性 是否共享 是否可变 应该存储在 ───────────────────────────────────────────── 字体名称 共享 不变 内部状态✅ 字体大小 共享 不变 内部状态✅ 文字内容 不共享 可变 外部状态✅ 屏幕位置(x, y) 不共享 可变 外部状态✅ 颜色 共享 不变 内部状态✅ 样式(粗体/斜体) 共享 不变 内部状态✅ 用户输入数据 不共享 可变 外部状态✅ ``` ### 实战例子:文本编辑器 ```java // ❌ 非享元方式 - 100万字符,每个都是独立对象 public class CharacterNormal { private char value; private String fontName; // 重复! private int fontSize; // 重复! private Color color; // 重复! private int positionX; // 不同 private int positionY; // 不同 // 每个字符占用: ~200bytes(假设) // 100万个: 200MB! 太大了 } // ✅ 享元方式 - 100万字符,但字体对象共享 public class CharacterFlyweight { // 内部状态 - 共享 private final char value; private final Font font; // 共享Font对象 private final Color color; // 共享Color对象 // 字符只占: ~64bytes // 100万个: 64MB ✅ 减少了70% public CharacterFlyweight(char v, Font f, Color c) { this.value = v; this.font = f; this.color = c; } } // 外部状态 - 单独维护 public class TextLine { private List<CharacterFlyweight> characters; private Map<Integer, Integer> positionMap; // 字符位置 public void display(Graphics g) { for (int i = 0; i < characters.size(); i++) { CharacterFlyweight ch = characters.get(i); int x = positionMap.get(i); // 外部位置 int y = currentLine * fontSize; g.drawCharacter(ch, x, y); } } } ``` --- ## 4种对象池实现方案 ### 方案1: 基础HashMap池(简单场景) ```java public class BasicFlyweightPool { private Map<String, Flyweight> pool = new HashMap<>(); public Flyweight get(String key) { return pool.computeIfAbsent(key, k -> createFlyweight(k) ); } private Flyweight createFlyweight(String key) { // 解析key并创建 // key格式: "Arial-12-Black" String[] parts = key.split("-"); return new Flyweight(parts[0], Integer.parseInt(parts[1]), parts[2]); } public int getPoolSize() { return pool.size(); } } // 使用 BasicFlyweightPool pool = new BasicFlyweightPool(); Flyweight f1 = pool.get("Arial-12-Black"); Flyweight f2 = pool.get("Arial-12-Black"); assert f1 == f2; // 完全相同的对象 ``` **优缺点**: - ✅ 简单直接 - ✅ 高效查找(O(1)) - ❌ 内存泄漏风险(对象永不释放) - ❌ 不适合大量短生命周期对象 ### 方案2: 弱引用池(自动清理) ```java public class WeakReferenceFlyweightPool { // WeakHashMap: key被GC时自动移除 private Map<String, WeakReference<Flyweight>> pool = new WeakHashMap<>(); public Flyweight get(String key) { WeakReference<Flyweight> ref = pool.get(key); Flyweight flyweight = (ref != null) ? ref.get() : null; if (flyweight == null) { // 对象被GC了,重新创建 flyweight = createFlyweight(key); pool.put(key, new WeakReference<>(flyweight)); } return flyweight; } private Flyweight createFlyweight(String key) { // 创建逻辑 } } // 使用 WeakReferenceFlyweightPool pool = new WeakReferenceFlyweightPool(); Flyweight f1 = pool.get("Arial-12"); // f1没有其他引用时,会被GC回收 // 下次get时,会重新创建 ``` **优缺点**: - ✅ 自动清理内存 - ✅ 无内存泄漏风险 - ❌ GC不确定性(可能频繁重建) - ❌ 适合内存充足的场景 ### 方案3: LRU缓存池(限制大小) ```java public class LRUFlyweightPool { private static final int POOL_SIZE = 1000; // LinkedHashMap + 访问顺序保证LRU private LinkedHashMap<String, Flyweight> cache = new LinkedHashMap<String, Flyweight>( POOL_SIZE, 0.75f, true // accessOrder=true → LRU ) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { // 超过容量时移除最旧的 return size() > POOL_SIZE; } }; public synchronized Flyweight get(String key) { return cache.computeIfAbsent(key, k -> createFlyweight(k) ); } private Flyweight createFlyweight(String key) { // 创建逻辑 } public int getHitRate() { // 统计命中率 } } // 使用 LRUFlyweightPool pool = new LRUFlyweightPool(); for (int i = 0; i < 10000; i++) { String key = "Font-" + (i % 500); // 500种字体 Flyweight f = pool.get(key); } System.out.println("缓存大小: " + pool.getSize()); // 最多1000 System.out.println("命中率: " + pool.getHitRate()); // > 90% ``` **优缺点**: - ✅ 可控的内存占用 - ✅ 高命中率 - ⚠️ 需要同步保护 - ⚠️ 较复杂的实现 ### 方案4: 分类对象池(多类型管理) ```java public class CategorizedFlyweightPool { private Map<Class<?>, Map<String, Object>> pools = new ConcurrentHashMap<>(); public <T> T get(Class<T> type, String key, Supplier<T> creator) { // 为每个类型维护独立的池 Map<String, Object> typePool = pools.computeIfAbsent( type, k -> new ConcurrentHashMap<>() ); // 在该类型的池中查询 Object obj = typePool.computeIfAbsent(key, k -> creator.get()); return type.cast(obj); } public void clearPool(Class<?> type) { pools.remove(type); } public int getPoolSize(Class<?> type) { return pools.getOrDefault(type, Collections.emptyMap()).size(); } } // 使用 CategorizedFlyweightPool pool = new CategorizedFlyweightPool(); // 字体池 Font arial12 = pool.get( Font.class, "Arial-12", () -> new Font("Arial", 12) ); // 颜色池 Color red = pool.get( Color.class, "RED", () -> new Color(255, 0, 0) ); // 音频池 AudioClip bark = pool.get( AudioClip.class, "dog-bark.wav", () -> new AudioClip(loadWav("dog-bark.wav")) ); // 统计 System.out.println("Font池大小: " + pool.getPoolSize(Font.class)); System.out.println("Color池大小: " + pool.getPoolSize(Color.class)); ``` **优缺点**: - ✅ 灵活的类型管理 - ✅ 类型隔离 - ✅ 易于扩展 - ⚠️ 实现相对复杂 --- ## 4个常见问题的深度解决 ### 问题1: 内部状态修改导致共享破坏 **症状**: 一个享元对象的状态被修改,影响了所有使用者 **反面示例(❌ 错误)**: ```java public class MutableFlyweight { private String fontName; private int fontSize; // ❌ 提供了setter - 破坏了享元原则 public void setFontSize(int size) { this.fontSize = size; } } // 使用中的灾难 Flyweight f1 = pool.get("Arial-12"); // 此时1000000个享元对象都指向这个字体 f1.setFontSize(24); // ❌ 修改了! // 结果:所有1000000个字符的字体都变了! ``` **解决方案(✅ 正确)**: ```java public final class ImmutableFlyweight { private final String fontName; private final int fontSize; private final Color color; // ✅ 使用final - 无法修改 public ImmutableFlyweight(String name, int size, Color c) { this.fontName = name; this.fontSize = size; this.color = c; } // ✅ 只有getter,没有setter public String getFontName() { return fontName; } public int getFontSize() { return fontSize; } public Color getColor() { return color; } // ✅ 如果需要"修改",返回新对象 public ImmutableFlyweight withFontSize(int newSize) { return new ImmutableFlyweight(fontName, newSize, color); } } // 使用 Flyweight f1 = pool.get("Arial-12"); Flyweight f2 = f1.withFontSize(24); // 返回新对象,不修改原对象 ``` ### 问题2: 外部状态泄漏 **症状**: 外部状态被存储在享元对象中,破坏了设计 **反面示例(❌ 错误)**: ```java // ❌ 外部状态混入享元 public class BadFlyweight { // 享元应该共享的 private Font font; // 外部状态,不应该在这里! private int positionX; // ❌ private int positionY; // ❌ private String content; // ❌ private long timestamp; // ❌ } // 后果: // - 享元对象体积不再小 // - 不能真正共享 // - GC压力未减少 ``` **正确做法(✅)**: ```java // ✅ 享元类只包含内部状态 public class Flyweight { private final Font font; private final Color color; // 没有任何外部状态! } // ✅ 外部状态单独维护 public class TextPosition { private Flyweight flyweight; // 指向享元 private int x, y; // 外部:位置 private String content; // 外部:内容 public void render(Graphics g) { g.drawText(flyweight, x, y, content); } } // ✅ 或者用Map维护 Map<FlyweightKey, ExternalState> externalStates; ``` ### 问题3: 线程安全问题
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub