享元设计模式
概述
享元设计模式(Flyweight Design Pattern)是一种用于性能优化的设计模式,它通过共享尽可能多的相似对象来减少对象的创建,从而降低内存使用和提高性能。享元模式的核心思想是将对象的共享部分提取出来,使得多个对象可以共享同一个享元对象,从而减少对象的创建。
定义:
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
结构
享元(Flyweight )模式中存在以下两种状态:
- 内部状态,即不会随着环境的改变而改变的可共享部分。
- 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
享元模式的结构如下:
-
抽象享元类(Flyweight):定义一个抽象类,包含一个享元对象的属性以及一个构造函数。
-
具体享元类(ConcreteFlyweight):实现抽象享元类,定义具体的享元对象。
-
享元工厂类(FlyweightFactory):负责创建和管理享元对象。享元工厂类会缓存已经创建的享元对象,当需要获取享元对象时,首先从缓存中查找,如果找不到,则创建一个新的享元对象并将其添加到缓存中。
享元模式的实现示例:
以下是一个简单的享元模式实现示例:
// 抽象享元类
public abstract class Flyweight {
public abstract void operation();
}
// 具体享元类
public class ConcreteFlyweight extends Flyweight {
private String state;
public ConcreteFlyweight(String state) {
this.state = state;
}
@Override
public void operation() {
System.out.println("ConcreteFlyweight: " + state);
}
}
// 享元工厂类
public class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String state) {
if (!flyweights.containsKey(state)) {
flyweights.put(state, new ConcreteFlyweight(state));
}
return flyweights.get(state);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("state1");
Flyweight flyweight2 = factory.getFlyweight("state2");
Flyweight flyweight3 = factory.getFlyweight("state1");
flyweight1.operation();
flyweight2.operation();
flyweight3.operation();
}
}
在这个示例中,我们定义了一个抽象享元类Flyweight
和一个具体享元类ConcreteFlyweight
。FlyweightFactory
类负责创建和管理享元对象,它使用一个HashMap
来缓存已经创建的享元对象。客户端通过FlyweightFactory
类来获取享元对象,并使用它们进行操作。
示例二:
/**
* @author OldGj 2024/02/29
* @version v1.0
* @apiNote 享元设计模式 - 抽象享元类
*/
public abstract class AbstractBox {
public abstract String getSharp();
public void display(String color) {
System.out.println("形状:" + this.getSharp() + ",颜色:" + color);
}
}
/**
* @author OldGj 2024/02/29
* @version v1.0
* @apiNote 具体享元类 - I图形
*/
public class IBox extends AbstractBox {
@Override
public String getSharp() {
return "I";
}
}
/**
* @author OldGj 2024/02/29
* @version v1.0
* @apiNote 具体享元类 - L图形
*/
public class LBox extends AbstractBox {
@Override
public String getSharp() {
return "L";
}
}
/**
* @author OldGj 2024/02/29
* @version v1.0
* @apiNote 具体享元类 - O图形
*/
public class OBox extends AbstractBox {
@Override
public String getSharp() {
return "O";
}
}
/**
* @author OldGj 2024/02/29
* @version v1.0
* @apiNote 图形工厂类 - 享元工厂类 【单例实现】
*/
public class BoxFactory {
private final Map<String, AbstractBox> map;
private static final BoxFactory factory = new BoxFactory();
private BoxFactory() {
map = new HashMap<>();
map.put("I", new IBox());
map.put("O", new OBox());
map.put("L", new LBox());
}
public static BoxFactory getInstance() {
return factory;
}
public AbstractBox createBox(String name) {
return map.get(name);
}
}
/**
* @author OldGj 2024/02/29
* @version v1.0
* @apiNote 客户端 - 测试类
*/
public class Client {
public static void main(String[] args) {
BoxFactory factory = BoxFactory.getInstance();
AbstractBox LBox = factory.createBox("L");
LBox.display("红色"); // 外部状态
AbstractBox oBox = factory.createBox("O");
oBox.display("黑色"); // 外部状态
AbstractBox IBox = factory.createBox("I");
IBox.display("白色"); // 外部状态
AbstractBox IBox2 = factory.createBox("I");
IBox2.display("蓝色"); // 外部状态
System.out.println("是否共享同一个对象:" + (IBox == IBox2));
}
}
优缺点:
享元模式的优点:
-
减少对象创建:享元模式通过共享相似对象,减少了对象的创建,从而降低了内存使用和提高了性能。
-
提高性能:享元模式通过减少对象创建,提高了性能。
-
方便管理:享元模式将对象的共享部分提取出来,使得对象可以轻松地管理和维护。
享元模式的缺点:
-
享元对象共享:享元对象共享可能会导致一些问题,例如线程安全问题。享元模式要求享元对象是线程安全的,这可能会导致性能下降。
-
享元对象状态:享元对象的状态可能会影响其他使用相同享元对象的客户端。享元模式要求享元对象的状态是独立的,这可能会导致享元对象的状态难以维护。
-
享元对象生命周期:享元对象的生命周期可能会比客户端使用的时间长,这可能会导致内存泄漏。享元模式要求享元对象具有较长的生命周期,这可能会导致内存使用增加。
使用场景:
享元模式通常用于以下场景:
-
对象数量多且相似:当对象数量多且相似时,享元模式可以显著减少对象的创建,提高性能。
-
对象共享属性多:当对象共享属性多时,享元模式可以减少内存使用,提高性能。
-
对象创建开销大:当对象创建开销大时,享元模式可以减少对象创建的开销,提高性能。
JDK源码解析
Integer类使用了享元模式。我们先看下面的例子:
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));
}
}
运行上面代码,结果如下:
为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下:
public class Demo {
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)127);
Integer i2 Integer.valueOf((int)127);
System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
Integer i3 = Integer.valueOf((int)128);
Integer i4 = Integer.valueOf((int)128);
System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
}
}
上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 valueOf()
,所以只需要看该方法即可
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
}
可以看到 Integer
默认先创建并缓存 -128 ~ 127
之间数的 Integer
对象,当调用 valueOf
时如果参数在 -128 ~ 127
之间则计算下标并从缓存中返回,否则创建一个新的 Integer
对象。