享元模式是很是经常使用的一种结构性设计模式。java
特别是在面试的时候。当咱们把这一节内容掌握,我相信不论是工做中仍是面试中这一块内容绝对是一大亮点。面试
所谓“享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。设计模式
具体来说,当一个系统中存在大量重复对象的时候,若是这些重复的对象是不可变对象,咱们就能够利用享元模式将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样能够减小内存中对象的数量,起到节省内存的目的。缓存
这里值得注意的是只保留一份实例,供多人使用。ide
我相信大伙在面试的时候常常会被问到String,Integer相关的面试题。函数
那咱们就从这两块内容开始讲解。优化
咱们先来看下面这样一段代码。ui
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是被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 类中要共享的对象,是在类加载的时候,就集中一次性建立好的。
可是,对于字符串来讲,咱们无法事先知道要共享哪些字符串常量,因此没办法事先建立好。
只能在某个字符串常量第一次被用到的时候,存储到常量池中,当以后再用到的时候,直接引用常量池中已经存在的便可,就不须要再从新建立了
咱们想一想,什么状况咱们应该使用享元模式。
我总结了一下:
我举一个具体的例子。
好比咱们开发一个麻将游戏。没一局游戏是否是要new一个麻将桌,new一副麻将。假如同时在线100w人,那咱们就new了25w个麻将桌和25w副麻。
咱们仔细想一想能不能用享元模式来优化,首先麻将桌应该是不能优化的,由于他得记录咱们每一局游戏得状态,桌上麻将的状况,等等信息。可是麻将咱们却能够缓存一副,让他不可变。全部人共用这一副缓存的麻将。
享元模式其实开发中咱们用的不是特别多,可是当须要时,却很是的有效。包括面试中关于String,基本类型的包装类关于享元模式的运用。当面试管再抛出这个问题,若是你能回答清楚而且提出其设计模式是享元模式,我相信必定会让面试官眼前一亮。