这里写目录标题
享元模式的定义和结构
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用,享元模式是构造型模式之一,它通过共享数据使得相同对象在内存中仅创建一个实例,以降低系统创建对象实例的性能消耗。
如JDK中的String的常量池,就是使用的享元模式。
public static void main(String[] args) throws UnknownHostException {
String a = "abc";
String b = "abc";
System.out.println(a == b);
}
结果如下:
true
当第一次创建abc时会在字符串常量池中常见一个“abc”的字符串,当再次使用时,直接将引用指向原先的字符串。同时享元模式是将变与不变分离,同时共享不变的内容。所以享元模式就有两种状态:内部状态和外部状态。
- 内部状态:即不会随着环境的改变而改变的可共享部分。
- 外部状态:指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。
怎么理解这个内部状态和外部状态呢?举个例子:
如上图所示,正常的话,如果使用代码来实现,我们是定义一个Circle类,其中定义颜色、坐标、直径等信息。由于存在对象复用的情况,所以我们使用享元模式,那么就需要考虑,那些是不变的,也就是通用的,那些是变的。可以看到颜色、直径是不变的,可以作为其内部装填,而坐标是可变的,取决于客户端调用时放到哪个坐标上,所以这里的颜色和直径就变为内部状态,而坐标作为外部状态。
那么享元模式的结构如下:
享元模式中包含以下几个角色:
- 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
- 享元工厂(FlyweightFactory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
享元模式的实现
抽象享元角色和具体享元:
interface Flyweight{
void operation(String extrinsicState);
}
class ConcreteFlyweight implements Flyweight{
private String intrinsicState;
ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
System.out.println("extrinsicState:" + extrinsicState);
System.out.println("intrinsicState:" + intrinsicState);
}
}
享元工厂:(享元工厂FlyweightFactory负责维护一个享元对象存储池(Flyweight Pool)来存放内部状态的对象。为了调用方便,该工厂类一般使用单例模式来实现。)
class FlyweightFactory {
private static FlyweightFactory instance = new FlyweightFactory();
private Map<String, Flyweight> flyweightMap = new HashMap<String, Flyweight>();
private FlyweightFactory() {
}
public static FlyweightFactory getInstance() {
return instance;
}
public Flyweight getFlyweight(String intrinsicState) {
Flyweight flyweight = flyweightMap.get(intrinsicState);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(intrinsicState);
flyweightMap.put(intrinsicState, flyweight);
}
return flyweight;
}
public Map<String, Flyweight> getFlyweightMap() {
return flyweightMap;
}
}
享元模式的适用性及优缺点
享元模式的适用性:
- 一个应用程序使用了大量的对象。
- 完全由于使用大量的对象,造成很大的存储开销。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
- 应用程序不依赖对象标识。
享元模式的优点:
- 减少对象数量,节省内存空间。
享元模式的缺点:
- 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
- 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长(
我理解的是由于将对象的状态分为内部状态和外部状态,获取到对象后可以直接通过对象获取其内部状态,但是外部状态需要通过计算或者其他方式获取,那么不能直接通过共享对象直接获取
)。
JDK中享元模式的使用
String中享元模式的使用
由于字符串是创建频率且复用频率较高,所以JVM中为字符串分配了字符串常量池,针对没有创建字符串的数据在字符串常量池中分配内存并创建,在后续再次使用时,直接将引用指向已创建的对象从而实现共享。
Short、Character、Integer和Long等使用享元模式减少对象的创建
在Short、Character、Integer中使用了享元模式,这里以Integer的valueOf方法来看,首先看下Integer的valueOf方法:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到这里使用了一个判断数据位于[IntegerCache.low,IntegerCache.high]之间则是直接去IntegerCache的cache数组中获取,那么来看一下IntegerCache:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
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) {
// If the property cannot be parsed into an int, ignore it.
}
}
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() {}
}
可以看到,在IntegerCache中有一个cache数组,在IntegerCache类初始化时,将指定范围的数值创建Integer实例,并放入数组中,默认范围为[-128,127]。