1、定义:
享元模式主要用于减少创建对象的数量,以减少内存占用和提升性能
2、优点:
大大较少对象的创建,降低系统的内存,使用率高。
3、缺点:
提高了系统的复杂度,需要分离出内部状态和外部状态,而且外部状态具有固有化的性质,不应该随着内部状态的改变而变化,否则会造成系统的混乱。
4、享元模式分类
1、单纯的享元模式:所有的享元对象都是可以共享的。
2、复合的享元模式:将多个单纯的享元对象组合到一起,复合的享元对象是不可以共享的,但是每个部分又是可以共享的单纯的享元对象。
单纯的享元模式:
一、结构组成
1、抽象享元类(FlyWeight):规定具体实现方法的公共接口。
2、具体享元类(ConcreteFlyweight):实现抽象的享元接口,同时遵守享元对象的一定法则。
3、享元工厂类(FlyweightFactory):负责创建和管理享元对象。
二、关系图
三、示例分析
抽象的享元接口Flyweight:
/*抽象的享元接口Flyweight*/
public interface Flyweight {
void operation(String externalState);
}
具体享元对象ConcreteFlyweight:
/*具体享元对象ConcreteFlyweight*/
public class ConcreteFlyweight implements Flyweight{
private String internalState;
/*
* @param internalState 内部状态
*/
public ConcreteFlyweight(String internalState) {
this.internalState = internalState;
}
/*
* 外部状态通过普通方法参数的形式传入,从而改变具体方法的行为,但是不改变内部状态
* @param externalState 外部状态
*/
@Override
public void operation(String externalState) {
System.out.println("internalState:" + internalState);
System.out.println("externalState:" + externalState);
}
}
享元工厂FlyweightFactory:
/*享元工厂FlyweightFactory*/
public class FlyweightFactory {
/*使用HashMap作为容器,保存具体的享元对象*/
private Map<String,Flyweight> flyweightMap = new HashMap<>();
public Flyweight getFlyweight(String internalState) {
/*先在当前HashMap容器中寻找有没有*/
Flyweight flyweight = flyweightMap.get(internalState);
/*这里没有考虑对线程同时访问的情况,具体可以使用单例模式。*/
if(flyweight == null) {
flyweight = new ConcreteFlyweight(internalState);
flyweightMap.put(internalState,flyweight);
}
return flyweight;
}
}
测试类:
public class TestMain {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight = factory.getFlyweight("first_internalState");
flyweight.operation("first_externalState");
System.out.println(flyweight);
System.out.println("--------------------------------------------");
flyweight.operation("second_externalState");
System.out.println(flyweight);
}
}
输出结果如下:
可以看出享元对象的内部属性并没有改变,并且享元对象并没有重新创建,即共享的。但是外部属性是可以变化的。
复合的享元模式:
一、结构组成
1、抽象的享元类(FlyWeight):规定具体实现方法的公共接口。
2、单纯的享元对象(ConcreteFlyweight):实现抽象的享元接口,同时遵守享元对象的一定法则。
3、复合的享元对象(CompositeConcreteFlyweight):复合的享元对象是不可以共享的,但是每一个部分又是可以共享的。
4、享元工厂类(FlyweightFactory):负责创建和管理单纯享元对象和复合享元对象。
二、关系图
三、示例分析
抽象的享元类Flyweight:
/*抽象的享元接口Flyweight*/
public interface Flyweight {
void operation(String externalState);
}
具体享元对象ConcreteFlyweight:
/*具体享元对象ConcreteFlyweight*/
public class ConcreteFlyweight implements Flyweight{
private String internalState;
/*
* @param internalState 内部状态
*/
public ConcreteFlyweight(String internalState) {
this.internalState = internalState;
}
/*
* 外部状态通过普通方法参数的形式传入,从而改变具体方法的行为,但是不改变内部状态
* @param externalState 外部状态
*/
@Override
public void operation(String externalState) {
System.out.println("internalState:" + internalState);
System.out.println("externalState:" + externalState);
}
}
复合的享元对象CompositeConcreteFlyweight:
/*复合的享元对象CompositeConcreteFlyweight*/
public class CompositeConcreteFlyweight implements Flyweight{
private Map<String,Flyweight> compositeConcreteFlyweight = new HashMap<>();
/*
* 添加单纯的享元对象
* @param internalState:内部状态
* @param flyweight:单纯的享元对象
*/
public void putFlyweight(String internalState,Flyweight flyweight) {
compositeConcreteFlyweight.put(internalState,flyweight);
}
/* 获取复合享元对象中的一个单纯的享元对象
* @param internalState:内部状态
*/
public Flyweight getFlyweight(String internalState) {
return compositeConcreteFlyweight.get(internalState);
}
@Override
public void operation(String externalState) {
/*遍历复合的享元对象中包含的每个单纯的享元对象*/
for (String internalState : compositeConcreteFlyweight.keySet()) {
Flyweight flyweight = compositeConcreteFlyweight.get(internalState);
flyweight.operation(externalState);
}
}
}
享元工厂FlyweightFactory:包含了单纯享元对象和复合享元对象的获取方法
/*享元工厂FlyweightFactory*/
public class FlyweightFactory {
/*使用HashMap作为容器,保存具体的享元对象*/
private Map<String,Flyweight> flyweightMap = new HashMap<>();
/* 获取单纯的享元对象
* @param internalStates:内部状态
*/
public Flyweight getFlyweight(String internalState) {
/*先在当前HashMap容器中寻找有没有*/
Flyweight flyweight = flyweightMap.get(internalState);
/*这里没有考虑对线程同时访问的情况,具体可以使用单例模式。*/
if(flyweight == null) {
flyweight = new ConcreteFlyweight(internalState);
flyweightMap.put(internalState,flyweight);
}
return flyweight;
}
/* 获取复合的享元对象
* @param internalStates 多个单纯的享元对象的内部状态
* @return
*/
public Flyweight getCompositeFlyweight(List<String> internalStates) {
CompositeConcreteFlyweight ccFlyweight = new CompositeConcreteFlyweight();
for (String internalState : internalStates) {
ccFlyweight.putFlyweight(internalState,getFlyweight(internalState));
}
return ccFlyweight;
}
}
测试类:
public class TestMain {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
List<String> internalStates = new ArrayList<>();
internalStates.add("first_internalState");
internalStates.add("second_internalState");
CompositeConcreteFlyweight compositeFlyweightA = (CompositeConcreteFlyweight)
factory.getCompositeFlyweight(internalStates);
CompositeConcreteFlyweight compositeFlyweightB = (CompositeConcreteFlyweight)
factory.getCompositeFlyweight(internalStates);
System.out.println("compositeFlyweightA和compositeFlyweightB是和可以共享的嘛?" +
(compositeFlyweightA==compositeFlyweightB));
System.out.println("-----------------------------------------------");
System.out.println("compositeFlyweightA中的first_internalState和compositeFlyweightB中的first_internalState是可以共享的嘛?" +
(compositeFlyweightA.getFlyweight(internalStates.get(0))==compositeFlyweightB.getFlyweight(internalStates.get(0))));
}
}
输出结果如下:
从测试结果可以得出,复合的享元对象是不可以共享的,但是复合享元对象中的每一个单纯的享元对象的可以共享的。
5、典型应用
一:String中的享元模式
Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0以前是位于常量池中,位于永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = "he" + "llo";
String s4 = "hel" + new String("lo");
String s5 = new String("hello");
String s6 = s5.intern();
String s7 = "h";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1==s2);//true
System.out.println(s1==s3);//true
System.out.println(s1==s4);//false
System.out.println(s1==s9);//false
System.out.println(s4==s5);//false
System.out.println(s1==s6);//true
}
}
String类的final修饰的,以字面量的形式创建String变量时,jvm会在编译期间就把该字面量hello放到字符串常量池中,由Java程序启动的时候就已经加载到内存中了。这个字符串常量池的特点就是有且只有一份相同的字面量,如果有其它相同的字面量,jvm则返回这个字面量的引用,如果没有相同的字面量,则在字符串常量池创建这个字面量并返回它的引用。
由于s2指向的字面量hello在常量池中已经存在了(s1先于s2),于是jvm就返回这个字面量绑定的引用,所以s1==s2。
s3中字面量的拼接其实就是hello,jvm在编译期间就已经对它进行优化,所以s1==s3。
s4中的new String("lo")生成了两个对象,lo,new String("lo"),lo存在字符串常量池,new String("lo")存在堆中,String s4 = "hel" + new String("lo")实质上是两个对象的相加,编译器不会进行优化,相加的结果存在堆中,而s1存在字符串常量池中,当然不相等。s1==s9的原理一样。
s4==s5两个相加的结果都在堆中,不用说,肯定不相等。
s1==s6中,s5.intern()方法能使一个位于堆中的字符串在运行期间动态地加入到字符串常量池中(字符串常量池的内容是程序启动的时候就已经加载好了),如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用,否则,创建复制一份该字面量到字符串常量池并返回它的引用。因此s1==s6输出true
二:Integer 中的享元模式
看下面的例子:
public static void main(String[] args) {
Integer i1 = 12 ;
Integer i2 = 12 ;
System.out.println(i1 == i2);
Integer b1 = 128 ;
Integer b2 = 128 ;
System.out.println(b1 == b2);
}
输出结果是:
true
false
为什么第一个是true,第二个是false?反编译后可以发现 Integer b1 = 128;
实际变成了 Integer b1 = Integer.valueOf(128);
,所以我们来看 Integer
中的 valueOf
方法的实现:
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ?
Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
//...省略...
}
IntegerCache 缓存类:
//是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。
private static class IntegerCache {
static final int low = -128;//区间的最低值
static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数
static final Integer cache[]; //缓存数组
static {
// high value may be configured by property
int h = 127;
//这里可以在运行时设置虚拟机参数来确定h :-Djava.lang.Integer.IntegerCache.high=250
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {//用户设置了
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//虽然设置了但是还是不能小于127
// 也不能超过最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
//循环将区间的数赋值给cache[]数组
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
可以看到 Integer
默认先创建并缓存 -128 ~ 127
之间数的 Integer
对象,当调用 valueOf
时如果参数在 -128 ~ 127
之间则计算下标并从缓存中返回,否则创建一个新的 Integer
对象。
三:Long中的享元模式
public final class Long extends Number implements Comparable<Long> {
public static Long valueOf(long var0) {
return var0 >= -128L && var0 <= 127L ? Long.LongCache.cache[(int)var0 + 128] : new Long(var0);
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
//...
}
同理,Long
中也有缓存,不过不能指定缓存最大值
四:数据库的数据池