con un clic
flyweight
使用共享来有效支持大量细粒度的对象
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
使用共享来有效支持大量细粒度的对象
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
以聚合根为边界,包含多个相关Entity和ValueObject的集合。保证数据一致性和事务边界。
在DDD中具有唯一身份标识和生命周期的对象,通过身份而非属性值相等判断。
封装复杂对象和聚合的创建过程,将创建职责从领域对象中剥离,保证聚合创建时的不变量满足。
没有身份标识,通过属性值判断相等的对象。不可变,通常代表领域中的度量或描述。
命令查询责任分离,将数据的写入操作和读取操作分别用不同的模型处理,优化各自的性能。
将DDD战略设计应用于微服务架构,限界上下文指导服务拆分,领域事件实现服务间通信。
| name | Flyweight |
| description | 使用共享来有效支持大量细粒度的对象 |
| license | MIT |
Flyweight 是一种结构型设计模式,通过共享细粒度对象来节省内存。
// 享元对象 - 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; // 同一对象!
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)
);
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;
}
}
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());
}
}
内部状态(Intrinsic State):
外部状态(Extrinsic State):
属性 是否共享 是否可变 应该存储在
─────────────────────────────────────────────
字体名称 共享 不变 内部状态✅
字体大小 共享 不变 内部状态✅
文字内容 不共享 可变 外部状态✅
屏幕位置(x, y) 不共享 可变 外部状态✅
颜色 共享 不变 内部状态✅
样式(粗体/斜体) 共享 不变 内部状态✅
用户输入数据 不共享 可变 外部状态✅
// ❌ 非享元方式 - 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);
}
}
}
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; // 完全相同的对象
优缺点:
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时,会重新创建
优缺点:
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%
优缺点:
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));
优缺点:
症状: 一个享元对象的状态被修改,影响了所有使用者
反面示例(❌ 错误):
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个字符的字体都变了!
解决方案(✅ 正确):
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); // 返回新对象,不修改原对象
症状: 外部状态被存储在享元对象中,破坏了设计
反面示例(❌ 错误):
// ❌ 外部状态混入享元
public class BadFlyweight {
// 享元应该共享的
private Font font;
// 外部状态,不应该在这里!
private int positionX; // ❌
private int positionY; // ❌
private String content; // ❌
private long timestamp; // ❌
}
// 后果:
// - 享元对象体积不再小
// - 不能真正共享
// - GC压力未减少
正确做法(✅):
// ✅ 享元类只包含内部状态
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;
症状: 多线程并发访问享元对象池时出现冲突
反面示例(❌ 不安全):
// ❌ 非线程安全
public class UnsafeFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
public Flyweight get(String key) {
if (pool.containsKey(key)) {
return pool.get(key);
}
// ❌ 两个线程可能同时执行到这里
Flyweight f = createFlyweight(key);
pool.put(key, f); // 重复创建!
return f;
}
}
// 场景:
// Thread1: 不存在,创建了Flyweight1
// Thread2: 同时在创建,创建了Flyweight2
// 结果:两个相同的对象,违反享元原则
解决方案1: 同步方法:
public class SynchronizedFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
public synchronized Flyweight get(String key) {
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
}
// 缺点:所有get操作都要等待,性能低
解决方案2: ConcurrentHashMap:
public class ConcurrentFlyweightPool {
private ConcurrentHashMap<String, Flyweight> pool =
new ConcurrentHashMap<>();
public Flyweight get(String key) {
// computeIfAbsent是原子的,不会重复创建
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
}
// 优势:并发性更好,只在必要时同步
解决方案3: 双重检查:
public class DoubleCheckFlyweightPool {
private volatile Map<String, Flyweight> pool =
new ConcurrentHashMap<>();
public Flyweight get(String key) {
Flyweight f = pool.get(key);
if (f == null) {
synchronized (this) { // 只在第一次创建时同步
f = pool.get(key);
if (f == null) {
f = createFlyweight(key);
pool.put(key, f);
}
}
}
return f;
}
}
// 折衷方案:性能与安全平衡
症状: 享元对象不释放,导致内存持续增长
反面示例(❌ 泄漏):
// ❌ 基本Map池 - 永不释放
public class LeakyPool {
private Map<String, Flyweight> pool = new HashMap<>();
public Flyweight get(String key) {
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
// ❌ 没有清理机制
}
// 使用场景导致泄漏:
for (int hour = 0; hour < 24; hour++) {
for (int minute = 0; minute < 60; minute++) {
// 每分钟创建新的时间戳字符串
String timestamp = hour + ":" + minute;
Flyweight f = pool.get("time-" + timestamp);
// 1440个不同的key,都留在池里!
}
}
// 结果:内存持续占用
解决方案1: 手动清理:
public class CleanableFlyweightPool {
private Map<String, Flyweight> pool = new HashMap<>();
private long lastCleanupTime = System.currentTimeMillis();
private static final long CLEANUP_INTERVAL = 60000; // 1分钟
public Flyweight get(String key) {
// 定期清理
if (System.currentTimeMillis() - lastCleanupTime > CLEANUP_INTERVAL) {
cleanup();
lastCleanupTime = System.currentTimeMillis();
}
return pool.computeIfAbsent(key, k -> createFlyweight(k));
}
private void cleanup() {
// 保留热门对象,删除冷门对象
pool.entrySet().removeIf(e -> shouldEvict(e.getKey()));
}
private boolean shouldEvict(String key) {
// 根据使用频率、年龄等判断
}
}
解决方案2: 使用WeakReference:
public class SelfCleaningPool {
private Map<String, WeakReference<Flyweight>> pool =
new WeakHashMap<>();
public Flyweight get(String key) {
WeakReference<Flyweight> ref = pool.get(key);
Flyweight f = (ref != null) ? ref.get() : null;
if (f == null) {
f = createFlyweight(key);
pool.put(key, new WeakReference<>(f));
}
return f;
}
}
// JVM自动GC,无需手动
// Google Docs: 处理数百万字符时的内存优化
public class DocumentCharacter {
// 内部状态 - 共享
private final Font font;
private final Color textColor;
private final Color backgroundColor;
// 外部状态 - 不共享
// (由外部Document管理)
}
public class Document {
private FontPool fontPool = new FontPool();
private List<DocumentCharacter> characters;
private int[] characterPositions;
public void insertCharacter(char ch, Font font, int position) {
DocumentCharacter dc = new DocumentCharacter(
fontPool.get(font), // 共享Font对象
defaultColor,
backgroundColor
);
characters.add(dc);
characterPositions[characters.size()-1] = position;
}
}
// Unity引擎中的大型粒子效果
public class Particle {
// 内部状态 - 共享
private final Mesh mesh;
private final Material material;
private final Texture texture;
private final Shader shader;
// 外部状态 - 不共享
// (由ParticleEmitter维护)
}
public class ParticleEmitter {
private ParticlePool particlePool;
private List<ParticleInstance> activeParticles;
public void emit(int count) {
for (int i = 0; i < count; i++) {
Particle particle = particlePool.getParticle("fire");
ParticleInstance instance = new ParticleInstance(
particle,
randomPosition(), // 外部
randomVelocity() // 外部
);
activeParticles.add(instance);
}
}
}
// 性能对比:
// 不用享元: 1000000 particles × 200bytes = 200MB
// 用享元: 1000 unique particles × 200bytes + 1000000 × 40bytes = 40MB ✅
// HikariCP连接池
public class ConnectionPool {
private ConcurrentHashMap<String, Connection> pool =
new ConcurrentHashMap<>();
public Connection getConnection(String url) {
return pool.computeIfAbsent(url, dbUrl ->
createConnection(dbUrl)
);
}
public void releaseConnection(String url, Connection conn) {
// 不关闭连接,放回池中复用
pool.put(url, conn);
}
}
// 性能收益:
// - 创建DB连接成本高 (1-10ms)
// - 享元复用: 创建1次, 使用1000次
// - 性能提升 100倍+
// Java String Pool 的享元实现
public class StringPool {
private static Map<String, String> pool = new HashMap<>();
public static String intern(String str) {
// String.intern()就是享元模式
return pool.computeIfAbsent(str, k -> str);
}
}
// 示例:
String s1 = "hello";
String s2 = "hello";
assert s1 == s2; // 同一个对象! 享元
// vs
String s3 = new String("hello");
String s4 = new String("hello");
assert s3 != s4; // 不同对象
assert s3.equals(s4)); // 内容相同
// SimpleDateFormat复用(享元)
public class DateFormatPool {
private Map<String, SimpleDateFormat> pool = new ConcurrentHashMap<>();
public String format(Date date, String pattern) {
SimpleDateFormat sdf = pool.computeIfAbsent(pattern, p ->
new SimpleDateFormat(p)
);
synchronized (sdf) { // SimpleDateFormat不线程安全
return sdf.format(date);
}
}
}
// 代替每次都new SimpleDateFormat
// 性能提升:构造成本很高 (分析正则表达式等)
// Android/Swing UI框架中的图标共享
public class IconCache {
private Map<String, Icon> cache = new ConcurrentHashMap<>();
public Icon getIcon(String name) {
return cache.computeIfAbsent(name, n ->
loadIcon(n) // 磁盘I/O一次
);
}
}
// 应用场景:
// 同一个图标被用在100个地方
// 不用享元:加载100次,占用内存100倍
// 用享元:加载1次,所有地方共享 ✅