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 filesShowing 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 GitHubThis SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub