《设计模式入门》 13.享元模式

享元模式(Flyweight Pattern)就是通过共享的方式高效的支持大量细粒度的对象。

一般情况下,享元模式应用于在有大量对象时,有可能会造成内存溢出的情况。我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

一般来说享元模式的结构有两个要求:

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;

  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

在JAVA中,这也是很常见的一种模式了,在java的String和Integer就使用了这种设计模式,极大的节省了创建对象内存空间。

=========================================================================

我们先看Integer中享元模式的使用:

package FlyweightPattern.IntegerFlyweight;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName FlyweightTest.java
 * @Description Integer 测试
 * @createTime 2022年03月08日 14:54:00
 */
public class FlyweightTest {
    public static void main(String[] args) {
        Integer a = Integer.valueOf(105);
        Integer b = 105;
        Integer c = Integer.valueOf(1000);
        Integer d = 1000;
        System.out.println("a==b:" + (a == b));
        System.out.println("c==d:" + (c == d));
    }
}

结果是:

 

之所以得到这样的结果,是因为 Integer 用到了享元模式。

我们看看Integer.valeOf()的源码:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

        在使用ValueOf的时候,首先JDK对int的值做了一个大小判断,先到缓存(IntegerCache)中找,能找到就直接返回缓存中的值,找不到的话再创建新的Integer对象

我们看看IntegerCache方法:

private static class IntegerCache {
    //最低值定位在-128
    static final int low = -128;
    //定义最高值为high,最高值可以通过property进行配置
    static final int high;
    //缓存池
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        
        //从配置中取high值
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");		
        //高值不为空
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                
                //i必须小于127
                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)
        //这个断言保证缓存池的范围在 -128~127之间
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

        可以看到,Integer在缓存中对常用值进行了预存储,如果我们新建的对象在缓存中已经预先生成了,我们就没必要去生成一个新的对象,节省了创建对象的性能消耗

=========================================================================

Integer b = 105;

这行代码在编译时,会自动的转成 :

Integer b = Integer.valueOf(105);

        创建b的时候调用方法,发现105在-128到127之间,所以先去IntegerCache中找。a和b找到的都是IntegerCache中预先创建好的函数。所以Integer a == integer b 是true

而对于c和d:

Integer c = Integer.valueOf(1000);

        他们的值超过了127,所以不会走缓存实现享元模式,而会直接创建对象,所以c和d是两个不同的对象。

=========================================================================

顺便对Integer做一下总结:

package FlyweightPattern.IntegerFlyweight;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName IntegerSummary.java
 * @Description 对Integer的总结
 * @createTime 2022年03月08日 16:12:00
 */
public class IntegerSummary {
    public static void main(String[] args) {

        Integer i1 = new Integer(100);
        Integer i2 = new Integer(100);
        Integer i3 = 100;
        Integer i4 = 100;
        int i5 = 100;

        Integer i6 = new Integer(1000);
        Integer i7 = 1000;
        Integer i8 = 1000;
        /*
            两个 new Integer() 变量比较 ,永远是 false
            因为new生成的是两个对象,其内存地址不同
         */
        System.out.println(i1 == i2);
        /*
            Integer变量 和 new Integer() 变量比较 ,永远为 false。
            因为 Integer变量 指向的是 java 常量池 中的对象,而 new Integer() 的变量指向 堆中 新建的对象,两者在内存中的地址不同。
         */
        System.out.println(i1 == i3);
        /*
            两个Integer 变量比较,如果两个变量的值在区间-128到127 之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为 false 。
            Integer i = 100 在编译时,会翻译成为 Integer i = Integer.valueOf(100)
            ava对于-128到127之间的数,会进行缓存。
            所以 Integer i = 127 时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。
         */
        System.out.println(i3 == i4);
        System.out.println(i7 == i8);
       /*
            int 变量 与 Integer、 new Integer() 比较时,只要两个的值是相等,则为true
            因为包装类Integer 和 基本数据类型int 比较时,java会自动拆包装为int ,然后进行比较,实际上就变为两个int变量的比较。
        */
        System.out.println(i1 == i5);
        System.out.println(i3 == i5);
    }
}

=========================================================================

然后我们模仿着写一个试试

我们首先创建一个享元对象的接口:

package FlyweightPattern;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName Flyweight.java
 * @Description 享元对象接口
 * @createTime 2022年03月08日 13:57:00
 */
public interface Flyweight {
    /**
     * 打印函数
     */
    void print();
}

然后定义一个实现类,实现打印方法输出对应的ID:

package FlyweightPattern;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName FlyweightImpl.java
 * @Description 创建具体享元对象
 * @createTime 2022年03月08日 16:18:00
 */
public class FlyweightImpl implements Flyweight{

    private String id;
    public FlyweightImpl(String id){
        this.id = id;
    }

    public String getId() {
        return id;
    }

    /**
     * 复写Print方法
     */
    @Override
    public void print() {
        System.out.println("id = "+ getId());
    }
}

创建工厂,保证享元对象的创建流程:

package FlyweightPattern;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName FlyweightFactory.java
 * @Description 创建工厂
 *              为了避免享元对象被重复创建,我们使用HashMap中的key值保证其唯一。
 * @createTime 2022年03月08日 16:20:00
 */
public class FlyweightFactory {

    /**
     * 为了避免享元对象被重复创建,我们使用HashMap中的key值保证其唯一。
     */
    private Map<String,Flyweight> flyweightMap = new HashMap<>();

    /**
     *  hashMap中没有值我们才创建,有的话就直接返回
     * @param str key
     * @return 享元对象
     */
    public Flyweight getFlyweight(String str){
        Flyweight flyweight = flyweightMap.get(str);
        if (flyweight == null){
            flyweight = new FlyweightImpl(str);
            flyweightMap.put(str,flyweight);
        }
        return flyweight;
    }

    /**
     *  获取hashMap的大小,也就是创建对象的数量
     * @return 创建对象的数量
     */
    public int getMapSize (){
        return  flyweightMap.size();
    }

}

 测试一下:

package FlyweightPattern;

/**
 * @author Zeyu Wan
 * @version 1.0.0
 * @ClassName FlyweightTestDemo.java
 * @Description 测试类
 * @createTime 2022年03月08日 16:25:00
 */
public class FlyweightTestDemo {
    public static void main(String[] args) {
        FlyweightFactory flyweightFactory = new FlyweightFactory();

        Flyweight flyweight1 = flyweightFactory.getFlyweight("A");
        Flyweight flyweight2 = flyweightFactory.getFlyweight("B");
        Flyweight flyweight3 = flyweightFactory.getFlyweight("A");

        flyweight1.print();
        flyweight2.print();
        flyweight3.print();

        System.out.println(flyweight1 == flyweight3);

        System.out.println(flyweightFactory.getMapSize());
    }
}

 

        可以看到,ID=A的对象只创建了一次,flyweight1和flyweight1指向了同一个对象,说明我们通过在hashmap中存储,避免了多次创建相同对象的无用性能消耗。

        

优点:

        大大减少对象的创建,降低系统的内存,使效率提高。

缺点:

        提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PigeonEssence

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值