java中interger享元模式_寂然解读设计模式 - 享元模式

I walk very slowly, but I never walk backwards

复制代码

设计模式 - 享元模式

寂然

大家好,我是寂然,本节课,我们来聊设计模式中的享元模式,老规矩,首先我们先通过一个案例需求来引入

案例演示 - 网站项目

首先我们来看这样一个需求:我们接了一个小型的外包项目,给客户老王做一个产品展示网站,老王的朋友们感觉效果不错,也希望做这样的产品展示网站,但是他们要求都有些不同

1)有客户要求以新闻的形式发布

2)有客户人要求以博客的形式发布

3)有客户希望以微信公众号的形式发布

解决方案一:一般实现

OK,拿到这样一个需求以后,我们先不要考虑今天要聊的享元模式,首要目标是解决需求,因为需求中虽然网站的发布形式不一样 ,但是基本的形式是一致的,所以最容易想到的方式是直接复制粘贴一份,然后根据客户不同要求,进行定制修改,接着,放到不同的虚拟空间中,如下示意图

4009df60ee64192ace0e098d1b122947.png

方案分析

其实上面实现方式是我们经常用的,复制一份进行定制修改,就要求客户需要的网站结构相似度很高,只是展示的形式和部分微观结构需要定制,而且这些都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,会造成服务器的资源浪费

那其实我们可以考虑这样做,整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都尝试达成一个共享的效果,这样不仅减少了需要的服务器资源,而且对于代码来说,由于是一份实例,维护和扩展都更加容易,这种解决思路其实就是享元模式

基本介绍享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象

属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式,享即共享,元即对象

常用于系统底层开发,解决系统的性能问题,像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则重新创建

再一个,享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似的对象,需要缓冲池时,不需要总是创建新对象,可以从缓存池里拿,这样可以降低系统内存,同时提高效率,所以,享元模式最经典的应用场景就是池技术了,String 常量池,数据库连接池,缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

原理类图

享元模式的类图如下图所示

1f469e49a09bed71ee4732122105a286.png

享元模式角色分析Flyweight 是抽象的享元角色,它是产品的抽象类,同时它会定义出对象的外部状态和内部状态

ConcreteFlyweight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务

UnsharedConcreteFlyweight 是不可共享的角色,这个角色可能会出现在享元模式中,但是一般不会出现在享元工厂中

FlyweightFactory 是享元工厂类,用于构建一个池的容器(以集合的形式展现),同时提供从池中获取对象的相关方法

外部状态&内部状态

享元模式提出了两个要求,细粒度和共享对象,这里涉及到了两个概念,内部状态与外部状态,即将对象的信息分为两个部分,内部状态与外部状态内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变

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

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,所以棋子颜 色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化 的,所以棋子坐标就是棋子的外部状态

为什么要这样做

围棋理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就减少了创建对象的数量,降低了重复对象的创建,减少内存占用和提高性能

解决方案二:享元模式

下面我们使用享元模式重构案例需求,首先我们通过类图来演示解决的思路

原理类图

93d99e29ed987cf4b7cbad025ad08fc1.png

代码演示//抽象的享元角色

public abstract class Website {

public abstract void show(User user);//抽象方法

}

//具体的享元角色

public class ConcreteWebsite extends Website {

//共享的部分,不会变属于内部状态

private String type = "";//网站发布的形式

//构造器

public ConcreteWebsite(String type) {

this.type = type;

}

//外部状态??例如用户

@Override

public void show(User user) {

System.out.println("以" + type + "形式发布网站项目,用户为:" + user.getName());

}

}

//享元工厂类

public class WebsiteFactory {

//创建集合,充当池的角色

private HashMap pool = new HashMap<>();

//根据网站的类型,返回对应的网站,如果没有就创建网站并放入池中

public Website getWebsite(String type){

if (!pool.containsKey(type)){

//如果没有对应的类型,就创建一个放入

pool.put(type,new ConcreteWebsite(type));

}

return (Website)pool.get(type);

}

//获取网站分类的总数

public int getWebsiteCount(){

return pool.size();

}

}

//用户

public class User {

private String name;

public User(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

//客户端

public class Client {

public static void main(String[] args) {

//创建一个工厂

WebsiteFactory websiteFactory = new WebsiteFactory();

//客户要一个以新闻形式发布的网站

Website news = websiteFactory.getWebsite("news");

news.show(new User("老李"));

//客户需要以微信公众号形式发布的网站

Website wechat = websiteFactory.getWebsite("Wechat");

wechat.show(new User("老王"));

//多个客户需要微信公众号形式发布

Website wechat1 = websiteFactory.getWebsite("Wechat");

wechat1.show(new User("老赵"));

Website wechat2 = websiteFactory.getWebsite("Wechat");

wechat2.show(new User("老沈"));

//看看池子里的总数

System.out.println(websiteFactory.getWebsiteCount());

}

}复制代码

注意事项

优势节省内存空间,重复对象需要频繁被创建时,由于只会被创建一次,所以对系统内存的需求也大大减小

提高效率,对于可重复的对象只会被创建一次,再次访问时先从缓冲池中获取,响应速度更快,效率更高

注意点

享元模式提高了系统的复杂度,需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方,使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制,如果盲目使用, 会提高系统逻辑的复杂度

使用场景享元模式经典的应用场景就是各类池技术

系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable等来进行存储

Integer源码分析

OK,其实我们最常用的 java.lang.Integer 中,就使用到了享元模式,这节课我们一起来看下 Integer 源码中,享元模式的使用,在此之前呢,我们一起来聊几道 Integer 相关的面试,作为前置基础来引出我们要聊的内容

Int&Integer面试题解析

首先我们来看如下几个案例,对结果进行判定Integer i = new Integer(100);

Integer j = new Integer(100);

System.out.print(i == j); //false

复制代码

由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)Integer i = new Integer(100);

int j = 100;

System.out.print(i == j); //true

复制代码

Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)Integer i = new Integer(100);

Integer j = 100;

System.out.print(i == j); //false

复制代码

非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)Integer i = 100;

Integer j = 100;

System.out.print(i == j); //true

Integer i = 128;

Integer j = 128;

System.out.print(i == j); //false复制代码

对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false

java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueof(100);通过源码我们得知,默认对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,不会再次进行new操作

测试代码

OK,明确了上面的几点后,我们来看这样一段测试代码public class Test {

public static void main(String[] args) {

Integer a = Integer.valueof(127);

Integer b = new Integer(127);

Integer c = Integer.valueof(127);

System.out.println(a.equals(b)); //比较大小,为true

System.out.println(a == b); //false

System.out.println(a == c); //true

}

}复制代码

有了上面的前置基础,判断就很容易了,同时上面我们聊到,说对于传入的127会进行缓存,a是通过valueof方法传入127返回的对象实例,c也是如此,所以为true,下面我们通过源码来验证一下这个结论,而且同时大家也会想到,这其中就使用到了享元模式

Integer源码解析

我们进入 Integer 的 valueof 方法,如果在定义好的范围以内,会直接从下面的缓存池里去拿,如果不再该范围以内,return new Integer(i);public static Integer valueof(int i) {

if (i >= IntegerCache.low && i <= IntegerCache.high)

return IntegerCache.cache[i + (-IntegerCache.low)];

return new Integer(i);

}复制代码

默认定义好的范围是多少呢?我们接着往下看,默认缓存的范围是-128到127之间的数

4aa5003f92259b5921b19cab718b298c.png

也就是说,如果 Integer.valueOf(x) ,x满足这个范围,就是使用享元模式返回,而享元模式中,如果第一次没有,进行创建,有就直接返回,如果不在该范围,仍然创建一个新的对象

接着看,它在第一次创建 IntegerCache 的时候,首先创建了 cache 数组,大小为 (high - low) + 1,是因为数据下标不可以为负数,也就是创建了可以存放 -128 - 127 之间的数组,然后往里面一个个添加数据

6dee45ee836fccf3a011b4a1ee60739a.png

valueOf 方法,就使用到享元模式,如果使用 valueOf 方法得到一个 Integer 实例,范围在 -128 - 127,直接返回已经创建好的,执行速度是要大于 new 操作的,当然,不光是 Integer 内部维护的缓冲池,String 常量池,数据库连接池,等等都是享元模式的应用,享元模式是池技术的重要实现方式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值