享元模式,”享元“ 就是被共享的单元。享元模式的意图就是复用对象节省内存,应用的前提是被共享的对象是不可变的对象。
将对象设计成享元,保留一份实例供多处代码引用这样能减少内存中对象的数量,不允许修改是因为避免一出修改影响其他使用他的代码。
问题:如果每个棋子都包含 id、文本、颜色、横坐标、纵坐标属性。并且每个游戏房间都有一个棋盘,那么如果游戏大厅中有成千上万个棋盘和对应的棋子。那么将创建大量的棋子对象。
方案:利用享元模式,象棋的棋子的一些属性,例如 id、颜色、文本都是固定不变的,下棋时每个棋盘的棋子也是一样的。所以只需要让棋子对象中保存 id、颜色、文本后被所有棋盘共享即可。每个房间只需要保存棋子的 id 和位置。这样就能节省大量的内存。
一个文本编辑器,每个输入的字符都可以单独调整文本样式,如果给每个字符都保存一个字符样式对象那么十分浪费内存。并且文本编辑器中,一篇文本往往只有少量的几个文本格式所以设计成享元模式能节省大量内存。
/**
* 文本样式类
*
* @author Jean
* @date 2024/06/04
*/
@Getter
public class CharsetStyle {
private int font;
private int fontSize;
private int colorRgb;
public CharsetStyle(int font, int fontSize, int colorRgb) {
this.font = font;
this.fontSize = fontSize;
this.colorRgb = colorRgb;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof CharsetStyle)) {
return false;
}
CharsetStyle other = (CharsetStyle) obj;
return other.font == this.font && this.fontSize == other.fontSize && this.colorRgb == other.colorRgb;
}
/**
* 重写了equals一般要重写hashcode方法
* 1. 如果两个对象根据 equals 方法判断是相等的,那么它们的 hashCode 方法必须返回相同的值。
* 2. 如果两个对象根据 equals 方法判断是不相等的,那么它们的 hashCode 方法不必返回不同的值,但建议这样做以提高散列表(如 HashMap)的性能。
* 违反这些规则可能会导致你的类在集合(尤其是基于散列的集合,如 HashSet 和 HashMap)中的行为不符合预期,比如无法正确识别已存在的元素或者影响集合的性能。因此,重写 equals 时配套重写 hashCode 是一种最佳实践。
*
* @return int
*/
@Override
public int hashCode() {
// 使用常数乘法、位移等操作来组合字段,以生成独特的哈希值
int result = 17;
result = 31 * result + font;
result = 31 * result + fontSize;
result = 31 * result + colorRgb;
return result;
}
}
/**
* 文本样式工厂
*
* @author Jean
* @date 2024/06/04
*/
public class CharacterStyleFactory {
/**
* 用来共享的文本样式
*/
private static final Set<CharsetStyle> styles = ConcurrentHashMap.newKeySet();
/**
* 获取样式的工厂
*
* @param font
* @param fontSize
* @param colorRgb
* @return {@link CharsetStyle}
*/
public static CharsetStyle getCharsetStype(int font, int fontSize, int colorRgb) {
CharsetStyle charsetStyle = new CharsetStyle(font, fontSize, colorRgb);
for (CharsetStyle style : styles) {
if (style.equals(charsetStyle)) {
return style;
}
}
styles.add(charsetStyle);
return charsetStyle;
}
}
Integer i1=56;
Integer i2=56;
Integer i3=129;
Integer i4=129;
System.out.pringln(i1==i2); //输出true
System.out.pringln(i3==i4); //输出false
例如上面的代码,会先输出 true 再输出 false
为什么默认是 -128~127 这个范围可调吗?
-Djava.lang.Integer.Cache.high=255 或者 -XX:AutoBoxCacheMax=255
Long、Short、Byte 等基础类型的包装类型都有缓存对应范围的对象
在 Java 中 有一个字符串常量池,在加载时一些字符串就会被创建到常量池中。后续引用到相同的字符串则可以从常量池中直接获取。这也是享元模式的一种应用。
应用享元模式前应该仔细测试是否真的在业务场景中能节省大量内存,否则可能适得其反。