当前位置: 首页 > news >正文

设计模式之:享元模式

文章目录

    • 什么是享元模式?
    • 核心思想
    • 生活中的享元模式
    • 模式结构
    • 基础示例:文字编辑器
      • 1. 享元接口和具体享元
      • 2. 享元工厂
      • 3. 客户端使用
      • 4. 测试客户端
    • 完整示例:围棋游戏
      • 1. 围棋棋子享元
      • 2. 围棋棋盘
      • 3. 游戏客户端
    • 实际应用示例:数据库连接池
    • 享元模式的优点
      • 1. 大幅减少内存使用
      • 2. 提高性能
      • 3. 更好的对象管理
    • 享元模式的缺点
      • 1. 增加系统复杂度
      • 2. 可能引入线程安全问题
    • 适用场景
    • 最佳实践
      • 1. 合理划分内部状态和外部状态
      • 2. 使用工厂管理享元对象
      • 3. 考虑线程安全性
    • 享元模式 vs 其他模式
    • 总结

什么是享元模式?

享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度对象的复用。享元模式通过共享已经存在的对象来减少内存使用,提高系统性能。

核心思想

享元模式的核心思想是:将对象的属性分为内部状态和外部状态,内部状态可以共享,外部状态由客户端维护,从而减少内存中对象的数量。

生活中的享元模式

想象一下字处理软件:

  • 每个字符都有字体、大小、颜色等属性
  • 如果每个字符都存储所有属性,内存会爆炸
  • 实际上,相同格式的字符共享格式信息
  • 只有字符内容和位置是每个字符独有的

这就是享元模式的思想!

模式结构

享元模式包含四个核心角色:

  1. 享元接口(Flyweight):定义享元对象的接口
  2. 具体享元(ConcreteFlyweight):实现享元接口,包含内部状态
  3. 享元工厂(FlyweightFactory):创建和管理享元对象
  4. 客户端(Client):维护外部状态,使用享元对象

基础示例:文字编辑器

1. 享元接口和具体享元

/*** 字符享元接口*/
public interface CharacterFlyweight {/*** 显示字符* @param externalState 外部状态(位置、颜色等)*/void display(CharacterExternalState externalState);/*** 获取字符内容*/char getCharacter();/*** 获取内部状态*/CharacterInternalState getInternalState();
}/*** 字符内部状态 - 可以共享的部分*/
class CharacterInternalState {private final String fontFamily;private final int fontSize;private final boolean isBold;private final boolean isItalic;public CharacterInternalState(String fontFamily, int fontSize, boolean isBold, boolean isItalic) {this.fontFamily = fontFamily;this.fontSize = fontSize;this.isBold = isBold;this.isItalic = isItalic;}// Getter方法public String getFontFamily() { return fontFamily; }public int getFontSize() { return fontSize; }public boolean isBold() { return isBold; }public boolean isItalic() { return isItalic; }@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;CharacterInternalState that = (CharacterInternalState) o;return fontSize == that.fontSize && isBold == that.isBold && isItalic == that.isItalic && Objects.equals(fontFamily, that.fontFamily);}@Overridepublic int hashCode() {return Objects.hash(fontFamily, fontSize, isBold, isItalic);}@Overridepublic String toString() {return String.format("字体:%s, 大小:%d, 粗体:%s, 斜体:%s", fontFamily, fontSize, isBold ? "是" : "否", isItalic ? "是" : "否");}
}/*** 字符外部状态 - 不可共享的部分*/
class CharacterExternalState {private final int positionX;private final int positionY;private final String color;public CharacterExternalState(int positionX, int positionY, String color) {this.positionX = positionX;this.positionY = positionY;this.color = color;}// Getter方法public int getPositionX() { return positionX; }public int getPositionY() { return positionY; }public String getColor() { return color; }@Overridepublic String toString() {return String.format("位置(%d,%d), 颜色:%s", positionX, positionY, color);}
}/*** 具体字符享元*/
public class ConcreteCharacter implements CharacterFlyweight {private final char character;private final CharacterInternalState internalState;public ConcreteCharacter(char character, CharacterInternalState internalState) {this.character = character;this.internalState = internalState;}@Overridepublic void display(CharacterExternalState externalState) {System.out.printf("字符 '%c' [%s] [%s]%n", character, internalState, externalState);}@Overridepublic char getCharacter() {return character;}@Overridepublic CharacterInternalState getInternalState() {return internalState;}
}

2. 享元工厂

import java.util.HashMap;
import java.util.Map;/*** 字符享元工厂* 管理字符享元对象的创建和共享*/
public class CharacterFlyweightFactory {private static CharacterFlyweightFactory instance;private final Map<String, CharacterFlyweight> characterPool;private CharacterFlyweightFactory() {this.characterPool = new HashMap<>();}public static CharacterFlyweightFactory getInstance() {if (instance == null) {instance = new CharacterFlyweightFactory();}return instance;}/*** 获取字符享元对象*/public CharacterFlyweight getCharacter(char character, CharacterInternalState internalState) {String key = generateKey(character, internalState);// 如果池中已存在,直接返回if (characterPool.containsKey(key)) {System.out.printf("✓ 共享字符: '%c' [%s]%n", character, internalState);return characterPool.get(key);}// 否则创建新的享元对象并放入池中System.out.printf("➤ 创建新字符: '%c' [%s]%n", character, internalState);CharacterFlyweight flyweight = new ConcreteCharacter(character, internalState);characterPool.put(key, flyweight);return flyweight;}/*** 生成享元对象的键*/private String generateKey(char character, CharacterInternalState internalState) {return character + "|" + internalState.hashCode();}/*** 获取池中享元对象数量*/public int getPoolSize() {return characterPool.size();}/*** 显示池中所有享元对象*/public void displayPool() {System.out.println("\n📊 字符享元池内容:");System.out.println("-".repeat(50));characterPool.forEach((key, flyweight) -> {ConcreteCharacter character = (ConcreteCharacter) flyweight;System.out.printf("键: %s -> 字符: '%c' [%s]%n", key, character.getCharacter(), character.getInternalState());});System.out.println("总计: " + characterPool.size() + " 个享元对象");}
}

3. 客户端使用

import java.util.ArrayList;
import java.util.List;
import java.util.Random;/*** 文档编辑器 - 客户端*/
public class DocumentEditor {private final List<CharacterContext> documentContent;private final CharacterFlyweightFactory factory;private final Random random;public DocumentEditor() {this.documentContent = new ArrayList<>();this.factory = CharacterFlyweightFactory.getInstance();this.random = new Random();}/*** 向文档添加字符*/public void addCharacter(char character, CharacterInternalState internalState, int positionX, int positionY, String color) {// 获取享元对象CharacterFlyweight flyweight = factory.getCharacter(character, internalState);// 创建外部状态CharacterExternalState externalState = new CharacterExternalState(positionX, positionY, color);// 保存字符上下文CharacterContext context = new CharacterContext(flyweight, externalState);documentContent.add(context);}/*** 显示文档内容*/public void displayDocument() {System.out.println("\n📄 文档内容:");System.out.println("=" .repeat(60));for (CharacterContext context : documentContent) {context.display();}System.out.println("=" .repeat(60));System.out.printf("文档字符数: %d, 享元对象数: %d, 节省内存: %.1f%%%n",documentContent.size(), factory.getPoolSize(),(1 - (double) factory.getPoolSize() / documentContent.size()) * 100);}/*** 生成测试文档*/public void generateTestDocument() {System.out.println("🛠️ 生成测试文档...");// 定义几种格式CharacterInternalState[] formats = {new CharacterInternalState("宋体", 12, false, false),  // 普通文本new CharacterInternalState("黑体", 14, true, false),   // 标题new CharacterInternalState("楷体", 12, false, true),   // 强调new CharacterInternalState("宋体", 10, false, false)   // 小字};String[] colors = {"黑色", "红色", "蓝色", "绿色"};String text = "享元模式通过共享技术来有效地支持大量细粒度对象的复用";int position = 0;for (char c : text.toCharArray()) {// 随机选择格式和颜色CharacterInternalState format = formats[random.nextInt(formats.length)];String color = colors[random.nextInt(colors.length)];addCharacter(c, format, position * 20, 0, color);position++;}}
}/*** 字符上下文 - 维护外部状态*/
class CharacterContext {private final CharacterFlyweight flyweight;private final CharacterExternalState externalState;public CharacterContext(CharacterFlyweight flyweight, CharacterExternalState externalState) {this.flyweight = flyweight;this.externalState = externalState;}public void display() {flyweight.display(externalState);}public CharacterFlyweight getFlyweight() {return flyweight;}public CharacterExternalState getExternalState() {return externalState;}
}

4. 测试客户端

/*** 享元模式测试客户端*/
public class FlyweightClient {public static void main(String[] args) {System.out.println("=== 享元模式演示 - 文字编辑器 ===\n");DocumentEditor editor = new DocumentEditor();// 演示1:基础使用demonstrateBasicUsage(editor);// 演示2:性能对比demonstratePerformanceComparison();// 演示3:实际应用场景demonstrateRealWorldScenario();}/*** 演示基础使用*/private static void demonstrateBasicUsage(DocumentEditor editor) {System.out.println("1. 基础使用演示:");System.out.println("-".repeat(50));// 创建几种格式CharacterInternalState normal = new CharacterInternalState("宋体", 12, false, false);CharacterInternalState bold = new CharacterInternalState("黑体", 14, true, false);CharacterInternalState italic = new CharacterInternalState("楷体", 12, false, true);// 添加一些字符editor.addCharacter('H', bold, 0, 0, "红色");editor.addCharacter('e', normal, 20, 0, "黑色");editor.addCharacter('l', normal, 40, 0, "黑色");editor.addCharacter('l', normal, 60, 0, "黑色"); // 重复字符,应该共享editor.addCharacter('o', italic, 80, 0, "蓝色");editor.addCharacter('!', bold, 100, 0, "红色");// 显示文档editor.displayDocument();// 显示享元池CharacterFlyweightFactory.getInstance().displayPool();}/*** 演示性能对比*/private static void demonstratePerformanceComparison() {System.out.println("\n2. 性能对比演示:");System.out.println("-".repeat(50));// 测试不使用享元模式long startTime = System.currentTimeMillis();testWithoutFlyweight();long withoutFlyweightTime = System.currentTimeMillis() - startTime;// 测试使用享元模式startTime = System.currentTimeMillis();testWithFlyweight();long withFlyweightTime = System.currentTimeMillis() - startTime;System.out.println("\n⏱️ 性能对比结果:");System.out.printf("不使用享元模式: %d ms%n", withoutFlyweightTime);System.out.printf("使用享元模式: %d ms%n", withFlyweightTime);System.out.printf("性能提升: %.1f%%%n", (1 - (double) withFlyweightTime / withoutFlyweightTime) * 100);}private static void testWithoutFlyweight() {List<SimpleCharacter> characters = new ArrayList<>();CharacterInternalState format = new CharacterInternalState("宋体", 12, false, false);// 创建10000个字符对象for (int i = 0; i < 10000; i++) {char c = (char) ('A' + (i % 26));characters.add(new SimpleCharacter(c, format, i * 10, 0, "黑色"));}}private static void testWithFlyweight() {DocumentEditor editor = new DocumentEditor();CharacterInternalState format = new CharacterInternalState("宋体", 12, false, false);// 创建10000个字符,但共享享元对象for (int i = 0; i < 10000; i++) {char c = (char) ('A' + (i % 26));editor.addCharacter(c, format, i * 10, 0, "黑色");}}/*** 演示实际应用场景*/private static void demonstrateRealWorldScenario() {System.out.println("\n3. 实际应用场景演示:");System.out.println("-".repeat(50));DocumentEditor editor = new DocumentEditor();editor.generateTestDocument();editor.displayDocument();CharacterFlyweightFactory.getInstance().displayPool();}
}/*** 简单字符类 - 用于对比测试*/
class SimpleCharacter {private char character;private CharacterInternalState internalState;private int positionX;private int positionY;private String color;public SimpleCharacter(char character, CharacterInternalState internalState,int positionX, int positionY, String color) {this.character = character;this.internalState = internalState;this.positionX = positionX;this.positionY = positionY;this.color = color;}
}

完整示例:围棋游戏

让我们通过一个更复杂的围棋游戏示例来深入理解享元模式。

1. 围棋棋子享元

import java.util.HashMap;
import java.util.Map;/*** 围棋棋子享元接口*/
public interface GoPiece {/*** 显示棋子* @param externalState 外部状态(位置)*/void display(PieceExternalState externalState);/*** 获取棋子颜色*/String getColor();
}/*** 棋子内部状态*/
class PieceInternalState {private final String color; // 颜色是内部状态,可以共享private final String shape; // 形状(圆形)public PieceInternalState(String color) {this.color = color;this.shape = "圆形";}public String getColor() { return color; }public String getShape() { return shape; }@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;PieceInternalState that = (PieceInternalState) o;return Objects.equals(color, that.color) && Objects.equals(shape, that.shape);}@Overridepublic int hashCode() {return Objects.hash(color, shape);}@Overridepublic String toString() {return color + shape;}
}/*** 棋子外部状态*/
class PieceExternalState {private final int x;private final int y;public PieceExternalState(int x, int y) {this.x = x;this.y = y;}public int getX() { return x; }public int getY() { return y; }@Overridepublic String toString() {return String.format("位置(%d,%d)", x, y);}
}/*** 具体围棋棋子*/
class ConcreteGoPiece implements GoPiece {private final PieceInternalState internalState;public ConcreteGoPiece(PieceInternalState internalState) {this.internalState = internalState;}@Overridepublic void display(PieceExternalState externalState) {System.out.printf("○ %s棋子 %s%n", internalState.getColor(), externalState);}@Overridepublic String getColor() {return internalState.getColor();}public PieceInternalState getInternalState() {return internalState;}
}/*** 围棋棋子工厂*/
class GoPieceFactory {private static GoPieceFactory instance;private final Map<String, GoPiece> piecePool;private GoPieceFactory() {this.piecePool = new HashMap<>();}public static GoPieceFactory getInstance() {if (instance == null) {instance = new GoPieceFactory();}return instance;}public GoPiece getPiece(String color) {// 如果池中已存在,直接返回if (piecePool.containsKey(color)) {System.out.printf("✓ 共享%s棋子%n", color);return piecePool.get(color);}// 否则创建新的享元对象System.out.printf("➤ 创建新%s棋子%n", color);PieceInternalState internalState = new PieceInternalState(color);GoPiece piece = new ConcreteGoPiece(internalState);piecePool.put(color, piece);return piece;}public int getPoolSize() {return piecePool.size();}public void displayPool() {System.out.println("\n🎲 棋子享元池:");piecePool.forEach((color, piece) -> {System.out.printf("颜色: %s -> %s%n", color, piece.getColor());});System.out.println("总计: " + piecePool.size() + " 种棋子");}
}

2. 围棋棋盘

import java.util.ArrayList;
import java.util.List;/*** 围棋棋盘*/
public class GoBoard {private final List<PieceContext> pieces;private final GoPieceFactory factory;private static final int BOARD_SIZE = 19;public GoBoard() {this.pieces = new ArrayList<>();this.factory = GoPieceFactory.getInstance();}/*** 在棋盘上放置棋子*/public void placePiece(String color, int x, int y) {if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) {System.out.println("❌ 无效位置: (" + x + "," + y + ")");return;}// 检查位置是否已有棋子if (hasPieceAt(x, y)) {System.out.println("❌ 位置 (" + x + "," + y + ") 已有棋子");return;}// 获取棋子享元GoPiece piece = factory.getPiece(color);// 创建外部状态PieceExternalState externalState = new PieceExternalState(x, y);// 保存棋子上下文PieceContext context = new PieceContext(piece, externalState);pieces.add(context);System.out.printf("✅ 在(%d,%d)放置%s棋子%n", x, y, color);}/*** 检查位置是否有棋子*/private boolean hasPieceAt(int x, int y) {return pieces.stream().anyMatch(context -> context.getExternalState().getX() == x && context.getExternalState().getY() == y);}/*** 显示棋盘状态*/public void displayBoard() {System.out.println("\n🎯 棋盘状态:");System.out.println("=" .repeat(40));for (PieceContext context : pieces) {context.display();}System.out.println("=" .repeat(40));System.out.printf("棋盘棋子数: %d, 享元对象数: %d%n", pieces.size(), factory.getPoolSize());}/*** 模拟一局围棋*/public void simulateGame() {System.out.println("🎮 开始模拟围棋对局...");// 黑棋先行placePiece("黑", 3, 3);placePiece("黑", 4, 4);placePiece("黑", 5, 5);// 白棋应对placePiece("白", 3, 4);placePiece("白", 4, 3);placePiece("白", 5, 4);// 继续对局placePiece("黑", 6, 6);placePiece("白", 6, 5);placePiece("黑", 7, 7);placePiece("白", 7, 6);// 尝试在已有位置放置棋子placePiece("黑", 3, 3); // 应该失败System.out.println("\n对局结束!");}
}/*** 棋子上下文*/
class PieceContext {private final GoPiece piece;private final PieceExternalState externalState;public PieceContext(GoPiece piece, PieceExternalState externalState) {this.piece = piece;this.externalState = externalState;}public void display() {piece.display(externalState);}public GoPiece getPiece() {return piece;}public PieceExternalState getExternalState() {return externalState;}
}

3. 游戏客户端

/*** 围棋游戏客户端*/
public class GoGameClient {public static void main(String[] args) {System.out.println("=== 享元模式演示 - 围棋游戏 ===\n");GoBoard board = new GoBoard();// 演示围棋游戏board.simulateGame();board.displayBoard();// 显示享元池GoPieceFactory.getInstance().displayPool();// 演示内存节省demonstrateMemorySaving();}/*** 演示内存节省效果*/private static void demonstrateMemorySaving() {System.out.println("\n💾 内存节省演示:");System.out.println("-".repeat(40));int totalPieces = 1000;GoPieceFactory factory = GoPieceFactory.getInstance();System.out.println("模拟放置 " + totalPieces + " 个棋子:");// 重置工厂// 在实际应用中,我们不会这样重置,这里只是为了演示System.out.println("创建黑白两种棋子享元...");// 模拟大量棋子放置GoPiece blackPiece = factory.getPiece("黑");GoPiece whitePiece = factory.getPiece("白");System.out.printf("实际创建的享元对象: %d 个%n", factory.getPoolSize());System.out.printf("内存节省: %d 个对象 -> %d 个享元, 节省 %.1f%% 内存%n",totalPieces, factory.getPoolSize(),(1 - (double) factory.getPoolSize() / totalPieces) * 100);}
}

实际应用示例:数据库连接池

import java.util.HashMap;
import java.util.Map;/*** 数据库连接池 - 享元模式实际应用*/
public class ConnectionPool {private static ConnectionPool instance;private final Map<String, DatabaseConnection> connectionPool;private final int maxPoolSize;private ConnectionPool(int maxPoolSize) {this.connectionPool = new HashMap<>();this.maxPoolSize = maxPoolSize;System.out.println("初始化数据库连接池,最大连接数: " + maxPoolSize);}public static ConnectionPool getInstance(int maxPoolSize) {if (instance == null) {instance = new ConnectionPool(maxPoolSize);}return instance;}/*** 获取数据库连接*/public DatabaseConnection getConnection(String databaseUrl, String username) {String key = databaseUrl + "|" + username;// 如果连接已存在且可用,直接返回if (connectionPool.containsKey(key) && connectionPool.get(key).isAvailable()) {System.out.println("✓ 复用数据库连接: " + key);return connectionPool.get(key);}// 如果连接池已满,等待或抛出异常if (connectionPool.size() >= maxPoolSize) {System.out.println("❌ 连接池已满,无法创建新连接");return null;}// 创建新连接System.out.println("➤ 创建新数据库连接: " + key);DatabaseConnection connection = new DatabaseConnection(databaseUrl, username);connectionPool.put(key, connection);return connection;}/*** 获取连接池状态*/public void displayPoolStatus() {System.out.println("\n📊 连接池状态:");System.out.println("当前连接数: " + connectionPool.size());System.out.println("最大连接数: " + maxPoolSize);System.out.println("使用率: " + (connectionPool.size() * 100 / maxPoolSize) + "%");}
}/*** 数据库连接 - 享元对象*/
class DatabaseConnection {private final String databaseUrl;private final String username;private boolean inUse;public DatabaseConnection(String databaseUrl, String username) {this.databaseUrl = databaseUrl;this.username = username;this.inUse = false;System.out.println("建立到 " + databaseUrl + " 的连接,用户: " + username);}public void executeQuery(String sql) {if (!inUse) {inUse = true;System.out.println("执行查询: " + sql);// 模拟查询执行try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}inUse = false;} else {System.out.println("连接正在使用中,无法执行查询");}}public boolean isAvailable() {return !inUse;}public String getConnectionInfo() {return databaseUrl + " (" + username + ")";}
}

享元模式的优点

1. 大幅减少内存使用

// 没有享元模式:创建1000个独立对象
// 使用享元模式:只创建2个享元对象(黑白棋子)
// 内存节省:998个对象!

2. 提高性能

// 对象创建和垃圾回收开销大大减少
// 特别适合大量细粒度对象的场景

3. 更好的对象管理

// 通过工厂统一管理享元对象
// 可以轻松实现对象池等功能

享元模式的缺点

1. 增加系统复杂度

// 需要区分内部状态和外部状态
// 增加了设计和实现的难度

2. 可能引入线程安全问题

// 如果享元对象有状态,需要考虑线程安全
// 外部状态需要由客户端正确管理

适用场景

  1. 系统中有大量相似对象时
  2. 对象的大部分状态可以外部化时
  3. 需要缓冲池的场景
  4. 需要实现对象共享的场景

最佳实践

1. 合理划分内部状态和外部状态

// 内部状态:不变的、可共享的
// 外部状态:变化的、不可共享的

2. 使用工厂管理享元对象

public class FlyweightFactory {// 统一管理享元对象的创建和获取// 确保对象的正确共享
}

3. 考虑线程安全性

// 如果享元对象有状态变化,需要同步控制
public synchronized Flyweight getFlyweight(String key) {// 线程安全的获取方式
}

享元模式 vs 其他模式

模式目的特点
享元模式对象共享减少内存使用,提高性能
单例模式唯一实例确保一个类只有一个实例
原型模式对象复制通过复制现有对象创建新对象

总结

享元模式就像是"资源共享中心",让相同的资源可以被多次使用,避免重复创建。

核心价值:

  • 大幅减少内存中对象的数量
  • 提高系统性能
  • 更好地管理相似对象

使用场景:

  • 系统中有大量相似对象时
  • 对象创建开销很大时
  • 需要实现对象池功能时

简单记忆:

大量对象不用愁,享元模式来帮忙!
内部状态可共享,外部状态客户端扛。

掌握享元模式,能够让你在处理大量相似对象时游刃有余,显著提升系统性能!

http://www.dtcms.com/a/520103.html

相关文章:

  • android 图像显示框架二——流程分析
  • CentOS 10 系统安装
  • MySQL试验部署
  • 【文献笔记】ICLR 2018 | Graph Attention Networks
  • Day69 SQLite3动态库移植 + BMP图像解析显示 + 进度条控件设计与动态文本管理
  • 通过自构建的时间服务器主机给客户端主机同步时间
  • [特殊字符] 软考架构师 vs. 考研408:全方位对比
  • C语言进阶:(一)深度剖析函数栈帧:从创建到销毁
  • 零基础从头教学Linux(Day 55)
  • 哪里有学做ppt的网站资阳的网站建设
  • Apple 开源FastVLM:AI看图说话更快更准
  • 交互式UTM坐标查询工具:让地理坐标转换变得简单
  • 初学者小白复盘15之指针(4)
  • 轻量级且简单的 macOS 应用 Forklift for mac
  • 和平板电脑厂商定制智慧养老平板有那种合作模式?
  • 无人机安防体系的音视频超低延迟重构:从“空地融合”到“实时智控”
  • 做网站推广业务怎么样专业仿站网站建设
  • 三分钟部署最新开源大模型!Amazon SageMaker JumpStart 生成式 AI 实战指南
  • AWS云服务故障复盘——从故障中汲取的 IT 运维经验
  • Adobe Dimension 2025 (3D可视化设计神器) 解锁版
  • CUDA安装备忘录
  • 泰安网站建设流程软文营销文章300字
  • 医院为什么要做门户网站建设无锡专业网站推广
  • freeRTOS学习
  • K8s 集群环境搭建 - yaml 版本(一)
  • RAM和ROM的定义和区别总结!!!
  • GELU(高斯误差线性单元)激活函数全面解析
  • 企业网站可以做淘宝客吗wordpress 用户密码加密
  • WordPress + React 无头架构搭建指南
  • 聚类算法实战:从 KMeans 到 DBSCAN