简介
享元模式,是对象池的一种实现,主要用于减少创建对象的数量,以减少内存占用和提高性能。定义:运用共享技术有效地支持大量细粒度的对象。意思就是采用对象共享的形式来实现大量对象的情况。有大量对象的情况,有可能导致内存溢出或者重复创建之前已经创建的相同对象。
先举个简单的例子,android
中的ViewHolder
缓存view
,为了优化性能存在一种写法,使用一个集合存储已经被实例化过的view
,就不需要每次都去创建了,这就是享元模式的一种简单应用。
public abstract class RvBaseViewHolder<T> extends RecyclerView.ViewHolder {
private SparseArray<View> mViews;
public RvBaseViewHolder(@NonNull View itemView) {
super(itemView);
mViews = new SparseArray<>();
}
public abstract void bindData(int position, @Nullable T data);
/**快速获取view*/
public <V extends View> V findViewById(int viewId){
View view = mViews.get(viewId);
if(null == view){
view = itemView.findViewById(viewId);
if(null != view){
mViews.put(viewId,view);
}else{
return null;
}
}
return (V) view;
}
}
UML图
- Flyweight:享元模式抽象类或接口
- ConcreateFlyweight:具体的享元对象
- FlyweightFactory:享元工厂,负责管理享元对象池和创建享元对象
Flyweight代表轻量级的意思。
简单示例
过年抢票,大家肯定都不陌生,各种刷票插件、软件什么的。在用户设置好出发和到达之后,每次查询请求都返回一系列的车票结果。当数千万的用户在不断请求查询结果时,如果每次查询结果都是重新创建返回的,可想而知,肯定会有大量的重复对象的创建、销毁,内存占用和GC的压力都会随之增大。而享元模式就能很好的应对这种情况,车次都是固定的,根据出发地和到达地查询出来的车次基本都是相同的(当然你还可以添加更多的筛选条件)。
我们可以将这些共享的对象缓存起来,用户查询时优先使用缓存,如果没有缓存则重新创建,这样就不必要在重复创建和销毁对象了。
首先,创建一个Ticket
接口,定义输出车票信息的方法
public interface Ticket {
public void showTicketInfo(String info);
}
再是具体的实现TrainTicket
类
public class TrainTicket implements Ticket {
//出发地
private final String from;
//到达地
private final String to;
//铺位
private String bunk;
//价格
private int price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void showTicketInfo(String bunk) {
price = new Random().nextInt(200);
System.out.println("查询 从 "+from+" 到 " + to + " 的 " + bunk + " 车票,价格:" + price);
}
}
接着就是TicketFactory
类,不同于之前的工厂模式,工厂模式每个返回的对象都是新创建的,而享元模式需要做缓存,具体代码如下:
public class TicketFactory {
private static Map<String,Ticket> ticketMap = new ConcurrentHashMap<>();
public static Ticket getTicket(String from,String to){
final String key = from + "-" + to;
if(ticketMap.containsKey(key)){
return ticketMap.get(key);
}else{
Ticket ticket = new TrainTicket(from, to);
ticketMap.put(key,ticket);
return ticket;
}
}
}
这就面了,每次获取车票对象时的创建,享元模式有效的减少了重复对象的创建。
Android中的享元模式
当我们需要用到Handler
发送信息的时候可能会注意到一点,并不是每次都是创建一个Message
对象,Handler
有个obtainMessage
方法其实还有几个重写方法,这个方法可以从已经创建过的Message
中重新获取一个Message
对象,以此来降低创建Message
对象的内存开销。
Handler
的obtainMessage
方法调用了Message
的obtain
方法,最后具体的代码如下:
public final class Message implements Parcelable {
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
//代码省略...
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
//代码省略...
}
其中sPoolSync
为同步锁对象,而sPool
是一个Message
对象,这里可能会有点奇怪为什么会叫sPool
却是一个Message
对象。其实这里Message
的实现是以列表的形式实现的,next
也是一个Message
对象指向的就是下一个Message
对象。这样子,这段代码大概就能理解了。
这里源码只说这么多,有兴趣的可以阅读源码。
总结
享元模式还是比较简单的,在创建重复对象的情况中大大降低了内存的消耗,提高了程序的性能。同时也提高了系统的复杂性,需要分离出外部状态和内部状态,而且外部状态应具有固化性,不应该随内部状态改变而改变,这样就使得程序变的很混乱。