设计模式之美笔记 —— 享元模式

目录

一、享元模式的意图

二、享元模式在Java Integer 和 String 中的应用

1、享元模式在Integer中的应用:

2、享元模式在String中的应用:

三、享元模式的垃圾回收问题


设计模式之美 - 54

一、享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。

享元模式 意在 复用
缓存
 意在 提高效率

具体来讲,当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。实际上,不仅仅相同对象可以设计成享元,对于相似对象,我们也可以将这些对象中相同的部分(字段)提取出来,设计成享元,让这些大量相似对象引用这些享元。

例如:

  1. 一个棋盘游戏中,可以将棋子的 id、text、color 属性拆分出来,设计成独立的类,并且作为享元供多个棋盘复用。这样,棋盘只需要记录每个棋子的位置信息就可以了。
  2. 一个文本编辑器,对于字体格式,我们可以将它设计成享元,让不同的文字共享使用。

设计模式之美 - 55

二、剖析享元模式在Java Integer 和 String 中的应用

1、享元模式在Integer中的应用:

面试题:以下代码输入结果是什么?

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129; 
System.out.println(i1 == i2); // 1 
System.out.println(i3 == i4); // 2 
  1. 如何判定两个 Java 对象是否相等(也就代码中的“==”操作符的含义)?
    当我们通过“==”来判定两个对象是否相等的时候,实际上是在判断两个局部变量存储的地址是否相同,换句话说,是在判断两个局部变量是否指向相同的对象。 JAVA中 == 和 equals 的区别?
  2. 什么是自动装箱(Autoboxing)和自动拆箱(Unboxing)?
    所谓的自动装箱,就是自动将基本数据类型转换为包装器类型。所谓的自动拆箱,也就是自动将包装器类型转化为基本数据类型。
Integer i = 56; 
// 数值 56 是基本数据类型 int,
// 当赋值给包装器类型(Integer)变量的时候,触发自动装箱操作,创建一个 Integer 类型的对象,并且赋值给变量 i。
int j = i; // 当把包装器类型的变量 i,
// 赋值给基本数据类型变量 j 的时候,触发自动拆箱操作,将 i 中的数据取出,赋值给 j

前 4 行赋值语句都会触发自动装箱操作,也就是会创建 Integer 对象并且赋值给 i1、i2、i3、i4 这四个变量。
根据自动装箱理论:i1、i2 尽管存储的数值相同,都是 56,但是指向不同的 Integer 对象,所以通过“==”来判定是否相同的时候,会返回 false。同理,i3==i4 判定语句也会返回 false。

但由于 Integer 用到了享元模式来复用对象,当我们通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象的时候,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中直接返回,否则才调用 new 方法创建。(除了 Integer 类型之外,其他包装器类型,比如 Long、Short、Byte 等,也都利用了享元模式来缓存 -128 到 127 之间的数据。)

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high) // JDK 也提供了方法来让我们可以自定义缓存的最大值
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  • 因为 56 处于 -128 和 127 之间,i1 和 i2 会指向相同的享元对象,所以 i1==i2 返回 true。
  • 而 129 大于 127,并不会被缓存,每次都会创建一个全新的对象,也就是说,i3 和 i4 指向不同的 Integer 对象,所以 i3==i4 返回 false。

缓存的享元对象只能选择缓存对于大部分应用来说最常用的整型值,也就是一个字节的大小(-128 到 127 之间的数据)。

 使用 自动装箱 或者 valueOf() 时候,才会用到享元。

当我们使用自动装箱或者 valueOf() 来创建这个数值区间的整型对象时,会复用 IntegerCache 类事先创建好的对象。这里的 IntegerCache 类就是享元工厂类,事先创建好的整型对象就是享元对象。

Integer a = new Integer(123); // 不会使用到整型的享元模式,所以不建议使用。
Integer a = 123;
Integer a = Integer.valueOf(123);

2、享元模式在String中的应用:

面试题:以下代码输入结果是什么?

String s1 = "小争哥";
String s2 = "小争哥";
String s3 = new String("小争哥"); // 不会使用到享元模式

System.out.println(s1 == s2); // 3
System.out.println(s1 == s3); // 4

使用 自动装箱 或者 valueOf() 时候,才会用到享元。

跟 Integer 类的设计思路相似,String 类利用享元模式来复用相同的字符串常量(也就是代码中的“小争哥”)。JVM 会专门开辟一块存储区来存储字符串常量,这块存储区叫作“字符串常量池”。上面代码对应的内存存储结构如下所示:

String 类的享元模式的设计,跟 Integer 类稍微有些不同:
Integer 类中要共享的对象,是在类加载的时候,就集中一次性创建好的。
但是,对于字符串来说,我们没法事先知道要共享哪些字符串常量,所以没办法事先创建好,只能在某个字符串常量第一次被用到的时候,存储到常量池中,当之后再用到的时候,直接引用常量池中已经存在的即可,就不需要再重新创建了。

三、享元模式的垃圾回收问题

jvm的垃圾回收机制

享元模式对 JVM 的垃圾回收并不友好。因为享元工厂类一直保存了对享元对象的引用,这就导致享元对象在没有任何代码使用的情况下,也并不会被 JVM 垃圾回收机制自动回收掉。因此,在某些情况下,如果对象的生命周期很短,也不会被密集使用,利用享元模式反倒可能会浪费更多的内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值