享元

运用共享技术有效地支持大量细粒度的对象。

享元(Flyweight)的核心思想很简单:如果一个对象实例一经创建就不可变,那么反复创建相同的实例就没有必要,直接向调用方返回一个共享的实例就行,这样即节省内存,又可以减少创建对象的过程,提高运行速度。

享元模式在Java标准库中有很多应用。我们知道,包装类型如ByteInteger都是不变类,因此,反复创建同一个值相同的包装类型是没有必要的。以Integer为例,如果我们通过Integer.valueOf()这个静态工厂方法创建Integer实例,当传入的int范围在-128~+127之间时,会直接返回缓存的Integer实例:

享元 - 图1

对于Byte来说,因为它一共只有256个状态,所以,通过Byte.valueOf()创建的Byte实例,全部都是缓存对象。

因此,享元模式就是通过工厂方法创建对象,在工厂方法内部,很可能返回缓存的实例,而不是新创建实例,从而实现不可变实例的复用。

总是使用工厂方法而不是new操作符创建实例,可获得享元模式的好处。

在实际应用中,享元模式主要应用于缓存,即客户端如果重复请求某些对象,不必每次查询数据库或者读取文件,而是直接返回内存中缓存的数据。

我们以Student为例,设计一个静态工厂方法,它在内部可以返回缓存的对象:

  1. public class Student {
  2. // 持有缓存:
  3. private static final Map<String, Student> cache = new HashMap<>();
  4. // 静态工厂方法:
  5. public static Student create(int id, String name) {
  6. String key = id + "\n" + name;
  7. // 先查找缓存:
  8. Student std = cache.get(key);
  9. if (std == null) {
  10. // 未找到,创建新对象:
  11. System.out.println(String.format("create new Student(%s, %s)", id, name));
  12. std = new Student(id, name);
  13. // 放入缓存:
  14. cache.put(key, std);
  15. } else {
  16. // 缓存中存在:
  17. System.out.println(String.format("return cached Student(%s, %s)", std.id, std.name));
  18. }
  19. return std;
  20. }
  21. private final int id;
  22. private final String name;
  23. public Student(int id, String name) {
  24. this.id = id;
  25. this.name = name;
  26. }
  27. }

在实际应用中,我们经常使用成熟的缓存库,例如GuavaCache,因为它提供了最大缓存数量限制、定时过期等实用功能。

练习

享元 - 图2下载练习:使用享元模式实现缓存 (推荐使用IDE练习插件快速下载)

小结

享元模式的设计思想是尽量复用已创建的对象,常用于工厂方法内部的优化。

读后有收获可以支付宝请作者喝咖啡,读后有疑问请加微信群讨论:

享元 - 图3享元 - 图4