【设计模式】享元模式(Flyweight)详解:用共享对象对抗内存爆炸

文章目录

    • [1. 引言:当对象多到撑爆内存](#1. 引言:当对象多到撑爆内存)
    • [2. 什么是享元模式](#2. 什么是享元模式)
      • [GoF 定义](#GoF 定义)
    • [3. 享元模式的核心思想](#3. 享元模式的核心思想)
    • [4. 享元模式的结构](#4. 享元模式的结构)
    • [5. 示例:字符系统](#5. 示例:字符系统)
      • [5.1 享元接口](#5.1 享元接口)
      • [5.2 具体享元](#5.2 具体享元)
      • [5.3 享元工厂](#5.3 享元工厂)
      • [5.4 客户端使用](#5.4 客户端使用)
    • [6. 享元模式的优点](#6. 享元模式的优点)
    • [7. 享元模式的缺点](#7. 享元模式的缺点)
    • [8. 享元 vs 对象池](#8. 享元 vs 对象池)
    • [9. JDK 中的享元模式](#9. JDK 中的享元模式)
      • [Integer 缓存](#Integer 缓存)
    • [10. 典型应用场景](#10. 典型应用场景)
    • [11. 一个常见误区](#11. 一个常见误区)
    • 参考

1. 引言:当对象多到撑爆内存

假设你在做一个文字编辑器,需要显示 100 万个字符:

latex 复制代码
Hello World ...

如果每个字符都 new 一个对象:

  • char
  • 字体
  • 颜色
  • 大小

内存会瞬间爆炸。

但实际上:

"a"和"a"是完全可以共享的。

这正是享元模式的出发点。

当对象多到内存扛不住时,就该考虑享元模式。


2. 什么是享元模式

GoF 定义

运用共享技术有效地支持大量细粒度对象。

详细解释:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。

Flyweight 是"轻量级"的意思,指的是拳击比赛中选手体重的最轻等级。顾名思义,该设计模式的作用是为了让对象更"轻"。

一句话理解:

把可以共享的部分抽出来,让对象变"轻"。


3. 享元模式的核心思想

享元模式的关键是区分两种状态:

状态 含义
内部状态 可以共享,如字符、颜色
外部状态 不可共享,如位置

共享的是内部状态,变化的是外部状态。

  • 内部状态,即不会随着环境的改变而改变的可共享部分。
  • 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

4. 享元模式的结构

包含四个角色:

  1. Flyweight(抽象享元)

通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。

  1. ConcreteFlyweight(具体享元)

它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

  1. FlyweightFactory(享元工厂)

负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

  1. Unsharable Flyweight(非享元角色)

并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。

  1. Client(客户端)

5. 示例:字符系统

5.1 享元接口

java 复制代码
public interface Character {
    void display(int x, int y);
}

5.2 具体享元

java 复制代码
public class ConcreteCharacter implements Character {

    private char symbol;   // 内部状态

    public ConcreteCharacter(char symbol) {
        this.symbol = symbol;
    }

    @Override
    public void display(int x, int y) {
        System.out.println(symbol + " 显示在 " + x + "," + y);
    }
}

5.3 享元工厂

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class CharacterFactory {

    private static Map<Character, Character> pool = new HashMap<>();

    public static Character get(char c) {
        pool.putIfAbsent(c, new ConcreteCharacter(c));
        return pool.get(c);
    }
}

5.4 客户端使用

java 复制代码
Character c1 = CharacterFactory.get('A');
Character c2 = CharacterFactory.get('A');

System.out.println(c1 == c2); // true

相同字符只创建一次对象。


6. 享元模式的优点

  1. 大幅节省内存
  2. 提高系统性能
  3. 支持海量对象
  4. 对客户端透明

7. 享元模式的缺点

  1. 系统复杂度增加
  2. 需要拆分内外部状态
  3. 调试难度变高

8. 享元 vs 对象池

维度 享元模式 对象池
目标 共享状态 复用对象
是否同时被多个使用
典型 字符、Integer 线程池、连接池

9. JDK 中的享元模式

Integer 缓存

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;

        System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));

        Integer i3 = 128;
        Integer i4 = 128;

        System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));
    }
}

结果如下:

JVM 对 -128 ~ 127 进行缓存,典型享元。

可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。


10. 典型应用场景

  • 字符串常量池
  • 数据库连接参数
  • 游戏地图中的格子
  • 图形系统(颜色、字体)

11. 一个常见误区

享元不是为了"减少 new",而是为了"共享状态"。


参考

享元模式 | 菜鸟教程

《图解设计模式》

享元 - Java教程 - 廖雪峰的官方网站

享元设计模式

相关推荐
程序员徐师兄13 分钟前
Windows JDK11 下载安装教程,适合新手
java·windows·jdk11 下载安装·jdk11 下载教程
RANCE_atttackkk21 分钟前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
五岳1 小时前
DTS按业务场景批量迁移阿里云MySQL表实战(下):迁移管理平台设计与实现
java·应用·dts
zhougl9961 小时前
Java 所有关键字及规范分类
java·开发语言
Python 老手1 小时前
Python while 循环 极简核心讲解
java·python·算法
java1234_小锋2 小时前
Java高频面试题:MyISAM索引与InnoDB索引的区别?
java·开发语言
Mr_Xuhhh2 小时前
MySQL函数详解:日期、字符串、数学及其他常用函数
java·数据库·sql
测试开发Kevin3 小时前
小tip:换行符CRLF 和 LF 的区别以及二者在实际项目中的影响
java·开发语言·python
笨手笨脚の3 小时前
Redis: Thread limit exceeded replacing blocked worker
java·redis·forkjoin·thread limit
Lenyiin3 小时前
Linux 基础IO
java·linux·服务器