享元模式(Flyweight Pattern)就是通过共享的方式高效的支持大量细粒度的对象。
一般情况下,享元模式应用于在有大量对象时,有可能会造成内存溢出的情况。我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
一般来说享元模式的结构有两个要求:
-
内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;
-
外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。
在JAVA中,这也是很常见的一种模式了,在java的String和Integer就使用了这种设计模式,极大的节省了创建对象内存空间。
=========================================================================
我们先看Integer中享元模式的使用:
package FlyweightPattern.IntegerFlyweight;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName FlyweightTest.java
* @Description Integer 测试
* @createTime 2022年03月08日 14:54:00
*/
public class FlyweightTest {
public static void main(String[] args) {
Integer a = Integer.valueOf(105);
Integer b = 105;
Integer c = Integer.valueOf(1000);
Integer d = 1000;
System.out.println("a==b:" + (a == b));
System.out.println("c==d:" + (c == d));
}
}
结果是:
之所以得到这样的结果,是因为 Integer 用到了享元模式。
我们看看Integer.valeOf()的源码:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在使用ValueOf的时候,首先JDK对int的值做了一个大小判断,先到缓存(IntegerCache)中找,能找到就直接返回缓存中的值,找不到的话再创建新的Integer对象。
我们看看IntegerCache方法:
private static class IntegerCache {
//最低值定位在-128
static final int low = -128;
//定义最高值为high,最高值可以通过property进行配置
static final int high;
//缓存池
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
//从配置中取high值
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//高值不为空
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
//i必须小于127
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)
//这个断言保证缓存池的范围在 -128~127之间
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
可以看到,Integer在缓存中对常用值进行了预存储,如果我们新建的对象在缓存中已经预先生成了,我们就没必要去生成一个新的对象,节省了创建对象的性能消耗。
=========================================================================
Integer b = 105;
这行代码在编译时,会自动的转成 :
Integer b = Integer.valueOf(105);
创建b的时候调用方法,发现105在-128到127之间,所以先去IntegerCache中找。a和b找到的都是IntegerCache中预先创建好的函数。所以Integer a == integer b 是true。
而对于c和d:
Integer c = Integer.valueOf(1000);
他们的值超过了127,所以不会走缓存实现享元模式,而会直接创建对象,所以c和d是两个不同的对象。
=========================================================================
顺便对Integer做一下总结:
package FlyweightPattern.IntegerFlyweight;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName IntegerSummary.java
* @Description 对Integer的总结
* @createTime 2022年03月08日 16:12:00
*/
public class IntegerSummary {
public static void main(String[] args) {
Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
Integer i3 = 100;
Integer i4 = 100;
int i5 = 100;
Integer i6 = new Integer(1000);
Integer i7 = 1000;
Integer i8 = 1000;
/*
两个 new Integer() 变量比较 ,永远是 false
因为new生成的是两个对象,其内存地址不同
*/
System.out.println(i1 == i2);
/*
Integer变量 和 new Integer() 变量比较 ,永远为 false。
因为 Integer变量 指向的是 java 常量池 中的对象,而 new Integer() 的变量指向 堆中 新建的对象,两者在内存中的地址不同。
*/
System.out.println(i1 == i3);
/*
两个Integer 变量比较,如果两个变量的值在区间-128到127 之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为 false 。
Integer i = 100 在编译时,会翻译成为 Integer i = Integer.valueOf(100)
ava对于-128到127之间的数,会进行缓存。
所以 Integer i = 127 时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
*/
System.out.println(i3 == i4);
System.out.println(i7 == i8);
/*
int 变量 与 Integer、 new Integer() 比较时,只要两个的值是相等,则为true
因为包装类Integer 和 基本数据类型int 比较时,java会自动拆包装为int ,然后进行比较,实际上就变为两个int变量的比较。
*/
System.out.println(i1 == i5);
System.out.println(i3 == i5);
}
}
=========================================================================
然后我们模仿着写一个试试
我们首先创建一个享元对象的接口:
package FlyweightPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Flyweight.java
* @Description 享元对象接口
* @createTime 2022年03月08日 13:57:00
*/
public interface Flyweight {
/**
* 打印函数
*/
void print();
}
然后定义一个实现类,实现打印方法输出对应的ID:
package FlyweightPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName FlyweightImpl.java
* @Description 创建具体享元对象
* @createTime 2022年03月08日 16:18:00
*/
public class FlyweightImpl implements Flyweight{
private String id;
public FlyweightImpl(String id){
this.id = id;
}
public String getId() {
return id;
}
/**
* 复写Print方法
*/
@Override
public void print() {
System.out.println("id = "+ getId());
}
}
创建工厂,保证享元对象的创建流程:
package FlyweightPattern;
import java.util.HashMap;
import java.util.Map;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName FlyweightFactory.java
* @Description 创建工厂
* 为了避免享元对象被重复创建,我们使用HashMap中的key值保证其唯一。
* @createTime 2022年03月08日 16:20:00
*/
public class FlyweightFactory {
/**
* 为了避免享元对象被重复创建,我们使用HashMap中的key值保证其唯一。
*/
private Map<String,Flyweight> flyweightMap = new HashMap<>();
/**
* hashMap中没有值我们才创建,有的话就直接返回
* @param str key
* @return 享元对象
*/
public Flyweight getFlyweight(String str){
Flyweight flyweight = flyweightMap.get(str);
if (flyweight == null){
flyweight = new FlyweightImpl(str);
flyweightMap.put(str,flyweight);
}
return flyweight;
}
/**
* 获取hashMap的大小,也就是创建对象的数量
* @return 创建对象的数量
*/
public int getMapSize (){
return flyweightMap.size();
}
}
测试一下:
package FlyweightPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName FlyweightTestDemo.java
* @Description 测试类
* @createTime 2022年03月08日 16:25:00
*/
public class FlyweightTestDemo {
public static void main(String[] args) {
FlyweightFactory flyweightFactory = new FlyweightFactory();
Flyweight flyweight1 = flyweightFactory.getFlyweight("A");
Flyweight flyweight2 = flyweightFactory.getFlyweight("B");
Flyweight flyweight3 = flyweightFactory.getFlyweight("A");
flyweight1.print();
flyweight2.print();
flyweight3.print();
System.out.println(flyweight1 == flyweight3);
System.out.println(flyweightFactory.getMapSize());
}
}
可以看到,ID=A的对象只创建了一次,flyweight1和flyweight1指向了同一个对象,说明我们通过在hashmap中存储,避免了多次创建相同对象的无用性能消耗。
优点:
大大减少对象的创建,降低系统的内存,使效率提高。
缺点:
提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。