👉戳我 (戳此处,速领)https://www.mockplus.cn/get-idoc?hmsr=qd-moon
激活码:MKCX-2024
## 什么是享元模式?
享元模式运用**共享技术**有效支持**大量细粒度对象**的复用。它通过共享已经存在的对象来减少内存使用,提高性能。
就像**共享单车**的比喻:
– 如果每个人都买一辆自行车,城市会拥堵不堪(内存爆炸)
– 共享单车让大家共用自行车(享元对象)
– 你只需要关心:谁在用?骑到哪里?(外部状态)
## 主要解决什么问题?
享元模式主要解决以下问题:
1. **内存消耗大**:创建大量相似对象导致内存溢出
2. **性能下降**:频繁创建和销毁对象影响性能
3. **重复对象多**:系统中存在大量相同或相似的对象
4. **缓存需求**:需要缓存对象以提高访问速度
## 何时使用享元模式?
当你遇到以下场景时,考虑使用享元模式:
– 系统有**大量相似对象**,消耗大量内存
– 对象的大多数状态可以**外部化**(可以移出对象)
– 对象可以被**共享**,且不依赖于具体上下文
– 需要**缓存**对象以提高性能
## 享元模式的优点
1. **减少内存**:大量相似对象共享同一份实例
2. **提高性能**:减少对象创建和GC压力
3. **缓存机制**:内置缓存提高访问速度
4. **状态分离**:内部状态和外部状态分离,设计更清晰
## 享元模式的缺点
1. **复杂度增加**:需要区分内部状态和外部状态
2. **线程安全问题**:共享对象需要考虑并发
3. **时间换空间**:需要额外计算来获取外部状态
## 代码示例:围棋游戏
让我们用一个围棋游戏的例子来演示享元模式。
### 1. 不使用享元模式的问题
“`java
// 如果不使用享元模式,每个棋子都是一个独立对象
class NaiveChessPiece {
private String color; // 颜色(黑/白)
private int x; // 横坐标
private int y; // 纵坐标
private String texture; // 棋子纹理(大对象)
public NaiveChessPiece(String color, int x, int y) {
this.color = color;
this.x = x;
this.y = y;
this.texture = loadTexture(color); // 加载图片,很消耗内存
}
private String loadTexture(String color) {
// 模拟加载纹理,实际会占用大量内存
return “棋子纹理数据_” + color + “_” + System.identityHashCode(this);
}
}
// 一盘围棋需要:19×19 = 361个棋子
// 每个棋子都包含完整的纹理数据 → 内存爆炸!
“`
### 2. 享元模式实现
“`java
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface ChessPiece {
void place(int x, int y); // 放置棋子(x,y是外部状态)
}
// 具体享元类:棋子类型(内部状态)
class ConcreteChessPiece implements ChessPiece {
private final String color; // 内部状态:颜色(可共享)
private final String texture; // 内部状态:纹理(可共享)
public ConcreteChessPiece(String color) {
this.color = color;
// 纹理只加载一次,所有同色棋子共享
this.texture = loadTexture(color);
System.out.println(“创建” + color + “棋子,加载纹理数据”);
}
private String loadTexture(String color) {
// 模拟加载纹理,实际会占用大量内存
return “棋子纹理数据_” + color;
}
@Override
public void place(int x, int y) {
// 外部状态:位置(不共享)
System.out.println(color + “棋子放置在(” + x + “, ” + y + “),使用纹理: ” + texture);
}
}
// 享元工厂:负责创建和管理享元对象
class ChessPieceFactory {
private static final Map<String, ChessPiece> pieceMap = new HashMap<>();
public static ChessPiece getChessPiece(String color) {
ChessPiece piece = pieceMap.get(color);
if (piece == null) {
// 如果没有该颜色的棋子,创建并放入缓存
piece = new ConcreteChessPiece(color);
pieceMap.put(color, piece);
} else {
System.out.println(“复用已有的” + color + “棋子”);
}
return piece;
}
public static int getTotalPieces() {
return pieceMap.size();
}
}
// 棋盘类(客户端)
class ChessBoard {
private ChessPiece[][] board = new ChessPiece[19][19];
public void placePiece(String color, int x, int y) {
// 获取享元对象
ChessPiece piece = ChessPieceFactory.getChessPiece(color);
// 存储引用(不是新建对象)
board[x][y] = piece;
// 放置棋子(传入外部状态)
piece.place(x, y);
}
public void displayBoard() {
System.out.println(“\n当前棋盘状态:”);
for (int i = 0; i < 19; i++) {
for (int j = 0; j < 19; j++) {
if (board[i][j] != null) {
System.out.print(“● “);
} else {
System.out.print(“○ “);
}
}
System.out.println();
}
}
}
// 客户端代码
public class FlyweightDemo {
public static void main(String[] args) {
System.out.println(“=== 享元模式示例:围棋游戏 ===\n”);
ChessBoard board = new ChessBoard();
// 下棋
System.out.println(“开始下棋:”);
board.placePiece(“黑色”, 3, 3);
board.placePiece(“白色”, 3, 4);
board.placePiece(“黑色”, 4, 3);
board.placePiece(“白色”, 4, 4);
board.placePiece(“黑色”, 5, 5);
board.placePiece(“白色”, 5, 6);
board.placePiece(“黑色”, 6, 5);
board.placePiece(“白色”, 6, 6);
System.out.println(“\n总共创建了 ” + ChessPieceFactory.getTotalPieces() + ” 个享元对象”);
System.out.println(“但棋盘上有 8 个棋子位置”);
board.displayBoard();
}
}
“`
### 3. 更复杂的例子:文字处理器
“`java
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
// 享元:字符样式
class CharacterStyle {
private final String font; // 字体
private final int size; // 字号
private final String color; // 颜色
private final boolean bold; // 粗体
private final boolean italic; // 斜体
public CharacterStyle(String font, int size, String color, boolean bold, boolean italic) {
this.font = font;
this.size = size;
this.color = color;
this.bold = bold;
this.italic = italic;
}
public void apply(String character, int position) {
System.out.println(“位置 ” + position + “: 字符 ‘” + character +
“‘ [字体=” + font + “, 大小=” + size +
“, 颜色=” + color + “, 粗体=” + bold +
“, 斜体=” + italic + “]”);
}
}
// 享元工厂
class CharacterStyleFactory {
private static final Map<String, CharacterStyle> styleMap = new HashMap<>();
public static CharacterStyle getStyle(String font, int size, String color,
boolean bold, boolean italic) {
// 生成唯一键
String key = font + “|” + size + “|” + color + “|” + bold + “|” + italic;
CharacterStyle style = styleMap.get(key);
if (style == null) {
style = new CharacterStyle(font, size, color, bold, italic);
styleMap.put(key, style);
System.out.println(“创建新样式: ” + key);
} else {
System.out.println(“复用已有样式: ” + key);
}
return style;
}
public static int getTotalStyles() {
return styleMap.size();
}
}
// 字符(包含外部状态)
class Character {
private final char ch; // 字符内容(外部状态)
private final int position; // 位置(外部状态)
private final CharacterStyle style; // 样式(享元对象)
public Character(char ch, int position, CharacterStyle style) {
this.ch = ch;
this.position = position;
this.style = style;
}
public void display() {
style.apply(String.valueOf(ch), position);
}
}
// 文档类
class Document {
private List<Character> characters = new ArrayList<>();
public void addCharacter(char ch, int position, String font, int size,
String color, boolean bold, boolean italic) {
// 获取享元样式
CharacterStyle style = CharacterStyleFactory.getStyle(font, size, color, bold, italic);
// 创建字符(使用享元)
Character character = new Character(ch, position, style);
characters.add(character);
}
public void display() {
System.out.println(“\n=== 文档内容 ===”);
for (Character c : characters) {
c.display();
}
}
}
// 客户端
public class TextEditorDemo {
public static void main(String[] args) {
System.out.println(“=== 文字处理器示例 ===\n”);
Document doc = new Document();
// 添加文本(大量重复样式)
System.out.println(“添加文本…”);
// 标题
doc.addCharacter(‘H’, 0, “Arial”, 24, “红色”, true, false);
doc.addCharacter(‘e’, 1, “Arial”, 24, “红色”, true, false);
doc.addCharacter(‘l’, 2, “Arial”, 24, “红色”, true, false);
doc.addCharacter(‘l’, 3, “Arial”, 24, “红色”, true, false);
doc.addCharacter(‘o’, 4, “Arial”, 24, “红色”, true, false);
// 正文
doc.addCharacter(‘W’, 5, “宋体”, 12, “黑色”, false, false);
doc.addCharacter(‘o’, 6, “宋体”, 12, “黑色”, false, false);
doc.addCharacter(‘r’, 7, “宋体”, 12, “黑色”, false, false);
doc.addCharacter(‘l’, 8, “宋体”, 12, “黑色”, false, false);
doc.addCharacter(‘d’, 9, “宋体”, 12, “黑色”, false, false);
// 强调
doc.addCharacter(‘!’, 10, “宋体”, 12, “红色”, true, true);
doc.addCharacter(‘!’, 11, “宋体”, 12, “红色”, true, true);
doc.addCharacter(‘!’, 12, “宋体”, 12, “红色”, true, true);
System.out.println(“\n总共创建了 ” + CharacterStyleFactory.getTotalStyles() + ” 种样式”);
System.out.println(“但文档中有 ” + 13 + ” 个字符”);
doc.display();
}
}
“`
### 4. 连接池示例
“`java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
// 数据库连接池(享元模式的应用)
class ConnectionPool {
private List<Connection> availableConnections = new ArrayList<>();
private List<Connection> usedConnections = new ArrayList<>();
private final int MAX_CONNECTIONS = 10;
private final String url;
private final String user;
private final String password;
public ConnectionPool(String url, String user, String password) {
this.url = url;
this.user = user;
this.password = password;
// 初始化连接池
for (int i = 0; i < 5; i++) {
availableConnections.add(createConnection());
}
}
private Connection createConnection() {
try {
System.out.println(“创建新的数据库连接”);
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
// 获取连接(类似享元工厂)
public synchronized Connection getConnection() {
if (availableConnections.isEmpty()) {
if (usedConnections.size() < MAX_CONNECTIONS) {
availableConnections.add(createConnection());
} else {
throw new RuntimeException(“连接池已满”);
}
}
Connection conn = availableConnections.remove(availableConnections.size() – 1);
usedConnections.add(conn);
System.out.println(“获取连接,可用连接数: ” + availableConnections.size());
return conn;
}
// 释放连接
public synchronized void releaseConnection(Connection conn) {
usedConnections.remove(conn);
availableConnections.add(conn);
System.out.println(“释放连接,可用连接数: ” + availableConnections.size());
}
public int getAvailableCount() {
return availableConnections.size();
}
}
// 简化的Connection实现(示例用)
class SimpleConnection implements Connection {
private String id;
public SimpleConnection(String id) {
this.id = id;
}
// 简化实现,只输出日志
public void close() {
System.out.println(“关闭连接: ” + id);
}
// … 其他方法省略
}
“`
### 5. 整数缓存(Java标准库中的享元)
“`java
public class IntegerCacheDemo {
public static void main(String[] args) {
System.out.println(“=== Java Integer缓存示例 ===\n”);
// Integer默认缓存 -128 到 127
Integer i1 = 127;
Integer i2 = 127;
System.out.println(“i1 == i2 (127): ” + (i1 == i2)); // true(享元复用)
Integer i3 = 128;
Integer i4 = 128;
System.out.println(“i3 == i4 (128): ” + (i3 == i4)); // false(超出缓存范围)
System.out.println(“\n=== String常量池示例 ===”);
String s1 = “hello”;
String s2 = “hello”;
String s3 = new String(“hello”);
System.out.println(“s1 == s2: ” + (s1 == s2)); // true(享元复用)
System.out.println(“s1 == s3: ” + (s1 == s3)); // false(new创建新对象)
System.out.println(“s1.equals(s3): ” + s1.equals(s3)); // true(内容相同)
}
}
“`
### 6. 图形渲染系统
“`java
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
// 享元:树类型
class TreeType {
private final String name; // 树种
private final String color; // 颜色
private final String texture; // 纹理(大对象)
public TreeType(String name, String color, String texture) {
this.name = name;
this.color = color;
this.texture = texture;
System.out.println(“加载树类型: ” + name + “,纹理: ” + texture);
}
public void draw(int x, int y) {
System.out.println(“在(” + x + “, ” + y + “)绘制” + color + “的” + name +
“,使用纹理: ” + texture);
}
}
// 享元工厂
class TreeFactory {
private static final Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String texture) {
String key = name + “|” + color + “|” + texture;
TreeType type = treeTypes.get(key);
if (type == null) {
type = new TreeType(name, color, texture);
treeTypes.put(key, type);
}
return type;
}
public static int getTypeCount() {
return treeTypes.size();
}
}
// 树对象(包含外部状态)
class Tree {
private final int x; // 外部状态:X坐标
private final int y; // 外部状态:Y坐标
private final TreeType type; // 内部状态:树类型(享元)
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void draw() {
type.draw(x, y);
}
}
// 森林(客户端)
class Forest {
private List<Tree> trees = new ArrayList<>();
public void plantTree(int x, int y, String name, String color, String texture) {
TreeType type = TreeFactory.getTreeType(name, color, texture);
Tree tree = new Tree(x, y, type);
trees.add(tree);
}
public void draw() {
System.out.println(“\n=== 绘制森林 ===”);
for (Tree tree : trees) {
tree.draw();
}
}
public void statistics() {
System.out.println(“\n=== 统计信息 ===”);
System.out.println(“树的总数: ” + trees.size());
System.out.println(“树类型数量: ” + TreeFactory.getTypeCount());
}
}
// 客户端
public class ForestDemo {
public static void main(String[] args) {
System.out.println(“=== 森林渲染系统 ===\n”);
Forest forest = new Forest();
// 种植大量树木(只有3种类型)
System.out.println(“开始种植树木…”);
// 种100棵橡树
for (int i = 0; i < 100; i++) {
forest.plantTree(i % 10, i / 10, “橡树”, “绿色”, “橡树纹理”);
}
// 种50棵松树
for (int i = 0; i < 50; i++) {
forest.plantTree(i % 10 + 10, i / 10, “松树”, “深绿”, “松树纹理”);
}
// 种30棵枫树
for (int i = 0; i < 30; i++) {
forest.plantTree(i % 10 + 20, i / 10, “枫树”, “红色”, “枫树纹理”);
}
forest.statistics();
// 绘制几棵树示例
System.out.println(“\n绘制示例:”);
forest.draw();
}
}
“`
## 实际应用场景
1. **Java标准库**:
– `Integer`、`Long`等包装类的缓存
– `String`常量池
– 线程池
2. **游戏开发**:
– 粒子系统(子弹、特效)
– 地图中的重复元素(树、建筑)
– NPC角色模型
3. **文本处理**:
– 文字处理器中的字符样式
– 编辑器中的语法高亮
4. **连接池**:
– 数据库连接池
– 线程池
– 对象池
## 内部状态 vs 外部状态
| 特性 | 内部状态 | 外部状态 |
|——|———|———|
| **定义** | 可共享的、不变的部分 | 不可共享的、变化的部分 |
| **存储** | 存储在享元对象中 | 由客户端存储或计算 |
| **是否可变** | 通常不可变 | 可以变化 |
| **例子** | 颜色、字体、纹理 | 位置、时间、上下文 |
## 享元模式 vs 其他模式
| 模式 | 目的 | 特点 |
|——|——|——|
| **享元模式** | 共享对象,节省内存 | 区分内外部状态 |
| **单例模式** | 确保一个类只有一个实例 | 全局唯一 |
| **对象池** | 复用对象,减少创建 | 预创建、借还机制 |
| **缓存模式** | 提高访问速度 | 存储计算结果 |
## 最佳实践
1. **明确区分状态**:仔细分析哪些是可共享的内部状态
2. **线程安全**:享元对象通常应该是线程安全的
3. **不要过度使用**:只有大量对象时才考虑
4. **考虑使用工厂**:配合工厂模式管理享元对象
5. **弱引用缓存**:可以使用WeakHashMap避免内存泄漏
## 总结
享元模式通过**共享技术**有效支持大量细粒度对象的复用,是解决内存爆炸的利器。
关键点:
– **内部状态**:可共享的、不变的部分
– **外部状态**:不可共享的、变化的部分
– **享元工厂**:管理享元对象的创建和缓存
– **客户端**:负责维护外部状态
记住:当你的系统需要创建**成千上万的相似对象**时,享元模式就是你的救星! 🎯✨
摹客超级版激活码领取链接👉戳我 (戳此处,速领)https://www.mockplus.cn/get-idoc?hmsr=qd-moon
激活码:MKCX-2024
云顶之弈-阵容搭配工具: 👉戳我 http://tftgamer.com(每赛季更新)
免费AI视频生成网站: 👉戳我 https://ai-text-to-video.com(ai text to video)









