徐州网站的优化,网站建设的具体代码,网页制作教程(第三版)书籍,亚洲做爰直播网站重修设计模式-结构型-享元模式 复用不可变对象#xff0c;节省内存 享元模式#xff08;Flyweight Pattern#xff09;核心思想是通过共享对象方式#xff0c;达到节省内存和提高性能的目的。享元对象需是不可变对象#xff0c;因为它会被多处代码共享使用#xff0c;要避…重修设计模式-结构型-享元模式 复用不可变对象节省内存 享元模式Flyweight Pattern核心思想是通过共享对象方式达到节省内存和提高性能的目的。享元对象需是不可变对象因为它会被多处代码共享使用要避免一处代码对享元进行了修改影响到其他使用它的代码。
享元模式的实现非常简单主要是通过工厂模式在工厂类中通过一个 Map 或者 List 来缓存已经创建过的享元对象来达到复用的目的。
举个例子在线象棋游戏
象棋可同时容纳上万个房间同时进行游戏每个房间最基础的是象棋和棋盘。一副象棋有32个棋子如果后每个房间都创建相同的棋子对象就是千万级别的对任何系统都是个挑战。
分析这个例子棋子的字和颜色是固定的几个跟场景无关场景相关的只有只有棋子的坐标。那其实可以把棋子不变的部分抽取出来设计为享元对象让所有棋子共享从而让每个棋子对象变得更轻量。
未使用享元模式的棋子对象
//棋子
data class ChessPieceOld(val id: Long,val text: String,val color: ChessPieceUnit.Color,val positionX: Int,val positionY: Int
) {
}当对象大量创建时几个固定的属性id、text、color会大量重复冗余占用内存。这时可以将这几个属性抽出设计为享元类并使用带缓存的工厂来生成享元对象
//棋子固定信息-享元类
data class ChessPieceUnit(val id: Long, val text: String, val color: Color) {enum class Color {RED, BLACK}
}//生成棋子-带缓存的创建工厂
class ChessPieceFactory {companion object {//享元对象池private val chessPiece hashMapOf(1 to ChessPieceUnit(1, 車, ChessPieceUnit.Color.RED),2 to ChessPieceUnit(2, 馬, ChessPieceUnit.Color.BLACK),//...)fun getChessPiece(id: Int): ChessPieceUnit {return chessPiece[id]!!}}
}//棋子
data class ChessPiece(val piece: ChessPieceUnit, val positionX: Int, val positionY: Int) {
}//棋盘
class ChessBoard() {private val chessPieces: HashMapInt, ChessPiece hashMapOf()fun init() {chessPieces.put(1, ChessPiece(ChessPieceFactory.getChessPiece(1), 0, 1))chessPieces.put(2, ChessPiece(ChessPieceFactory.getChessPiece(1), 0, 1))//...}fun move(chessPieceId: Int, positionX: Int, positionY: Int) {//...}
}使用享元后所有棋子的固定部分共同引用数量有限的享元对象每个棋子对象变得更为轻量内存中存储的冗余信息大大减少避免了大量相似对象的开销提高了系统资源的利用率。
注意上面的例子使用享元模式后棋子对象的数量并没有变化只是对象比之前小了很多。消耗内存最多的成员变量已经被移动到很少的几个享元对象中了这几个享元对象会被上千个情境小对象复用无需再重复存储相同数据。
享元模式在 Java 语言中的应用
1.Java 中的字符串常量池
String 类会利用享元模式来复用相同的字符串常量当某个字符串常量第一次被用到的时候存储到常量池中之后再用到的时候直接引用常量池中已经存在的即可不需要重复创建对象通过下面代码可以验证。
//字符串常量池
String s1 秋意浓;
String s2 秋意浓;
String s3 new String(秋意浓);//直接new出对象绕过了Java的常量池优化
System.out.println(s1 s2); //输出true
System.out.println(s1 s3); //输出false2.基本类型包装类
基本类型的包装类如Integer 、Long、Short、Byte 等也都利用了享元模式来缓存 -128 到 127 之间的数据。因为对于大部分应用来说这个区间是最常用的数值如果预先创建所有值的享元对象不仅会占用大量内存也会让类加载时间过长。
以 Integer 类型为例
//包装类型的享元
Integer n1 1;
Integer n2 1;
Integer n3 129;
Integer n4 129;
System.out.println(n1 n2); //输出true
System.out.println(n3 n4); //输出false因为 129 超出了享元对象池区间所以每次会返回新的对象两个对象地址不同输出 false。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 {// high value may be configured by propertyint 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_VALUEh 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() {}
}可以通过 JDK 参数修改缓存池上限下限不支持修改
//方法一
-Djava.lang.Integer.IntegerCache.high255
//方法二
-XX:AutoBoxCacheMax255在使用 Java 进行编码时要尽量避免直接 new 出包装类型或者字符类型对象如 String s new String(aaa)、Integer n new Integer(1)这样会绕过系统的享元模式创建重复对象。推荐直接使用自动装箱语法如Integer n 1或通过类型工厂的方式Integer n Integer.valueof(1)
上面两个场景都是享元模式在 Java 中的应用只是实现略有不同Integer 类中要共享的对象是在类加载的时候就一次性创建好的String 类的享元是在某个字符串第一次被用到的时候才创建并存储到常量池中的相当于懒加载方式。
享元模式的类似设计
1.享元模式 vs 单例
虽然享元模式的实现和单例的变体多例非常相似但它们的设计意图不同享元模式是为了对象复用节省内存。而多例是为了限制对象的个数。
2.享元模式 vs 对象池
虽然享元模式和对象池都是为了复用但他们的“复用”也是不同的概念
对象池中的“复用”可以理解为“重复使用”使用时被使用者独占使用完成后放回池中主要目的是节省时间。享元模式中的“复用”可以理解为“共享使用”在整个生命周期中都是被所有使用者共享的主要目的是节省空间。
总结
当一个系统中存在大量重复对象的时候我们就可以利用享元模式将对象设计成享元在内存中只保留一份实例供多处代码引用这样可以减少内存中对象的数量以起到节省内存的目的。