设计模式 --面试高频之享元模式

前言

享元模式是非常常用的一种结构性设计模式。

特别是在面试的时候。当我们把这一节内容掌握,我相信不管是工作中还是面试中这一块内容绝对是一大亮点。

什么是享元模式

所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。

具体来讲,当一个系统中存在大量重复对象的时候,如果这些重复的对象是不可变对象,我们就可以利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样可以减少内存中对象的数量,起到节省内存的目的。

这里值得注意的是只保留一份实例,供多人使用。

面试最常见的面试题

我相信大伙在面试的时候经常会被问到String,Integer相关的面试题。

那我们就从这两块内容开始讲解。

享元模式在Integer中的应用

我们先来看下面这样一段代码。

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2); //true
System.out.println(i3 == i4); //false

我相信很多人在面试的时候会遇到这种题目。答案可能会出乎我们的意料。第一个为true,第二个为false。

这正是因为 Integer,用到了享元模式来复用对象,才导致了这样的运行结果。当我们通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象的时候,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中直接返回,否则才调用 new 方法创建。看代码更加清晰一些,Integer 类的 valueOf() 函数的具体代码如下所示:

//从这里的源码我们能看到,当我们执行Integer i2 = 56;
//这行代码的时候。其实是通过自动装箱机制,调用的valueOf。
//当数据在IntegerCache.low~IntegerCache.high之间的时候,我们是直接从缓存中拿取的数据。
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

那这个IntegerCache是什么呢?这个其实是Integer的内部类。

我们挑选重点代码来看看,源码如下:


/**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */
private static class IntegerCache {
    static final int low = -128; //缓存的最小值
    static final int high; //缓存的最大值
    static final Integer cache[];  //缓存

    static {
        // high value may be configured by property
        int 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_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)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

这个是Integer的静态内部类,当我们加载Ineger的时候该类也会被加载进去。可以看到他缓存了-128 到 127 之间的整型值。

实际上,除了 Integer 类型之外,其他包装器类型,比如 Long、Short、Byte 等,也都利用了享元模式来缓存 -128 到 127 之间的数据。比如,Long 类型对应的 LongCache 享元工厂类及 valueOf() 。

其实jdk考虑的很周到,我们大部分时间创建出来的Ineger对象,其实都是存储整型都不是特别大。所以干脆取一段大小合理的数据直接缓存下来。

举一个极端一点的例子,假设程序需要创建 1 万个 -128 到 127 之间的 Integer 对象。使用第一种创建方式,我们需要分配 1 万个 Integer 对象的内存空间;使用后两种创建方式,我们最多只需要分配 256 个 Integer 对象的内存空间。

享元模式在String中的应用

我们都知道String是被final修饰的,大家又仔细想过这其中的缘由吗?

这最大的原因就是为了实现字符串池化技术。其核心思想就是享元模式。

我们前面提到过享元对象都是不可变的。这样我们才能保证大家在共同使用的时候不会出现问题。所以String是被final修饰的。

我们再来看一下这段代码:

String s1 = "享元模式";
String s2 = "享元模式";
String s3 = new String("享元模式");

System.out.println(s1 == s2); //ture
System.out.println(s1 == s3); //false

前两个s1和s2都是指向的字符串常量池的"享元模式"。而s3指向的是堆的String。

String 类的享元模式的设计,跟 Integer 类稍微有些不同。
Integer 类中要共享的对象,是在类加载的时候,就集中一次性创建好的。

但是,对于字符串来说,我们没法事先知道要共享哪些字符串常量,所以没办法事先创建好。

只能在某个字符串常量第一次被用到的时候,存储到常量池中,当之后再用到的时候,直接引用常量池中已经存在的即可,就不需要再重新创建了

实际运用

我们想想,什么情况我们应该使用享元模式。

我总结了一下:

  1. 首先这个对象在很多地方都得使用,否则就是过度设计。
  2. 其次这个对象是不可变的,可以让多个线程同时使用。

我举一个具体的例子。

比如我们开发一个麻将游戏。没一局游戏是不是要new一个麻将桌,new一副麻将。假如同时在线100w人,那我们就new了25w个麻将桌和25w副麻。

我们仔细想想能不能用享元模式来优化,首先麻将桌应该是不能优化的,因为他得记录我们每一局游戏得状态,桌上麻将的情况,等等信息。但是麻将我们却可以缓存一副,让他不可变。所有人共用这一副缓存的麻将。

总结

享元模式其实开发中我们用的不是特别多,但是当需要时,却非常的有效。包括面试中关于String,基本类型的包装类关于享元模式的运用。当面试管再抛出这个问题,如果你能回答清楚并且提出其设计模式是享元模式,我相信一定会让面试官眼前一亮。

(0)

相关推荐

  • 吊打面试官系列:说说Integer缓存范围

    回复"000"获取大量电子书 本文主要大致思路为: 不管从工作中还是面试,这篇文章都应该好好看完,本人认为是非常有用的. 案例 Integer是基本类型int的封装类.平时不管是入 ...

  • PHP设计模式之享元模式

    PHP设计模式之享元模式 享元模式,"享元"这两个字在中文里其实并没有什么特殊的意思,所以我们要把它拆分来看."享"就是共享,"元"就是元素 ...

  • [PHP小课堂]PHP设计模式之享元模式

    [PHP小课堂]PHP设计模式之享元模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 设计模式之享元模式

    享元模式 Flyweight Intro 享元是指一个可复用的对象,通过复用这个享元来减少应用中的内存分配. 享元模式是为了减少内存占用,尽可能复用已有对象的设计模式,一般来说会把这个可复用的对象放到 ...

  • 设计模式-享元模式

    定义 运用共享技术有效地支持大量细粒度的对象. 适用场景 例如,数据库连接,线程的创建开销都比较大,并且创建频率也非常高,因此就需要用到数据库连接池技术和线程池技术来共享数据库连接和线程.再例如,应用 ...

  • 无废话设计模式(9)结构型模式--享元模式

    0-前言 享元模式定义:运用共享技术有效地支持大量细粒度的对象. 1-实现 1-1.简单UML图:  1-2.代码实现 //1.抽象父类(网站父类) abstract class Website { ...

  • 设计模式(11) 享元模式

    基于面向对象思想设计的应用程序有时遇到需要场景大量相同或显示对象实例的场景,这些数量庞大的实例很可能会消耗很多系统资源,最直接的就是内存了.比如要一款围棋游戏,如果每次落子都新建一个对象,将会占用大量 ...

  • 泡图书馆,我想到了 享元模式

    回复"000"获取程序员必备电子书 大家好,我是老田,今天我给大家分享设计模式中的享元模式.用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式. 另外, ...

  • Flyweight享元模式

    >>返回<C#常用设计模式> 1. 简介 2. 示例 1. 简介 定义 使用共享对象可有效地支持大量的细粒度的对象. 解决问题 面向对象技术可以很好地解决一些灵活性或可扩展性问 ...

  • 结构型模式之享元模式

    在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题.创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈.例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网 ...