在内存优化的道路中,往往会遇到对大量使用对象的情况,为了减少内存的分配,减少垃圾回收器调用次数,合理的使用对象设计模式去设计对象,减少内存抖动的风险。对于对象的重用,主要有2中设计模式:对象池模式和享元模式
对象池模式
思想:为避免一个可能会被重用的对象,被垃圾回收。从而节省了创建对象所花费的时间和内存的重新分配。使用一个对象池的对象(万物皆对象)去负责管理大量可重用的对象,是重用的对象被请求在再次使用。
角色
- ReusableObject 可重用对象 被客户端使用,被对象池管理
- Client 客户端 请求者,需要使用对象干事情,向对象池获取可用的对象
- ObjectPool 对象池 持有可重用对象,负责管理对象:创建 分配 回收
注意点
- 对象池所包含的对象会有上限(必然的,无上限,就是无限创建,那对内存无任何帮助)
- 如果客户端请求ReusableObject时,对象池已满,所以对象正在被使用中。那么请求会被延迟,知道其他客户端归还可重用对象。
- 当刚被用完的对象,在传递给下一个客户端使用前,需要恢复到一个默认的状态,不能让可能收到一个不可预期的对象,同时也方便管理这些对象。
- 该模式在多线程环境中使用,需要处理线程安全,避免并发修改对象池中对象的情况

举个例子
客人在餐厅里吃饭,需要找个桌子。如果人很多,桌子不够用了,就需要等待。同时如果客人吃完饭,就需要服务员来清理桌子,让别的客人进来就餐。
这里可以看出餐厅的桌子可以看出ReusableObject 可重用对象,客人就想当如客户端,整个餐厅和服务员可以看成对象池。
给出一个简单的代码
package com.example.hasee.utils;
import android.util.SparseArray;
public abstract class ObjectPool<T> {
/**
* 空闲的
*/
private SparseArray<T> freePool;
/**
* 被使用的
*/
private SparseArray<T> lentPoll;
/**
* 最大容量
*/
private int maxCapacity;
/**
* 构造函数
*
* @param initialCapacity 初始化容量
* @param maxCapacity 最大容量
*/
public ObjectPool(int initialCapacity, int maxCapacity) {
initialize(initialCapacity);
this.maxCapacity = maxCapacity;
}
public ObjectPool(int maxCapacity) {
this(maxCapacity/2,maxCapacity);
}
/**
* 预先创建的对象
* @param initialCapacity
*/
private void initialize(final int initialCapacity) {
lentPoll = new SparseArray<>();
freePool = new SparseArray<>();
for (int i = 0; i < initialCapacity; i++) {
freePool.put(i,create());
}
}
/**
* 获取对象方法
* @return 对象
*/
public T acquire(){
T t = null;
synchronized (freePool){
int freeSize = freePool.size();
for (int i = 0; i < freeSize; i++) {
//通过位置查找key的值
int key = freePool.keyAt(i);
//获取对象
t = freePool.get(key);
if(t != null){
//被借出去了,lentPoll加入该对象
this.lentPoll.put(key,t);
//同理,空闲的移除
this.freePool.remove(key);
return t;
}
}
//为空时,并且借出的大小 加上 空闲大小 小于 总容量,说明可以创建 要不然只能等待
if(t == null && lentPoll.size() + freeSize < maxCapacity){
t = create();
lentPoll.put(lentPoll.size() + freeSize,t);
}
}
return t;
}
/**
* 创建对象,子类实现
* @return t
*/
protected abstract T create();
public void release(T t){
if(t == null){
return;
}
int key = lentPoll.indexOfValue(t);
restore(t);
this.freePool.put(key,t);
this.lentPoll.remove(key);
}
/**
* 重置对象
* @param t
*/
protected void restore(T t){
//重置这块 可以自己定义 不一定null就是初始化状态
t = null;
};
}
享元模式
思想
通过节省所有对象共享状态,以减少载入内存的量。客户所请求的对象可分为两种类型的状态。
- Internal state:内部状态。 由所有能够唯一标识一个对象的字段组成,并且这些字段不能与其他对象共享。
- External state:外部状态。在所有可交换对象之间能够共享的字段组合。
所以享元模式所做的是,为所有对象只创建一个实例,实例内部状态的重用,减少复制它所带来的消耗。

使用范围
享元模式适用于大量粒度对象的复用,该模式只需要使用少量对象即可实现大量对象的复用,前提是这些对象的粒度小,变化性,对象之间比较相似。其内部状态存储于对象内部,一般在构造时或者通过setter函数设置,这些状态不随外界环境变化而变化。外部状态由客户报错,一般在请求的时候,会将外部状态通过参数传入对象中,这些状态随外界环境变化而变化。
角色
- FlyWeightObjects: 享元对象。它们可以改变内部状态,并访问内部对象。
- FlyWeightFactory: 享元工厂。当客户请求享元对象时,享元工厂负责创建享元对象,管理享元对象的内部状态。它也负责将享元对象存储在在池中,方便提供对象。
- Clients: 客户。请求享元对象。
实例代码
public class PackCourier implements Courier<Pack> {
/**
* 状态辅助类
*/
private Van van;
/**
*
* id 作为内部状态
* @param id
*/
public PackCourier(int id) {
van = new Van(id);
}
/**
* 该方法将外部状态传入享元对象
*/
@Override
public void equip(Pack param) {
van.load(param);
}
}
class Delivery {
private Courier<Pack> courier;
public Delivery(int id){
courier = new Factory().getCourier(0);
}
/**
* 传入外部状态
* @param pack
*/
public void deliver(Pack pack){
courier.equip(pack);
}
}
class Factory{
private static SparseArray<Courier> pool;
public Factory(){
if(pool == null){
pool = new SparseArray<>();
}
}
public Courier getCourier(int type){
Courier courier = pool.get(type);
if(courier == null){
courier = create(type);
pool.put(type,courier);
}
return courier;
}
private Courier create(int type){
Courier courier = null;
courier = new PackCourier(type);
return courier;
}
}
/**
* 外部状态
*/
class Pack {
}
class Van {
public Van(int id) {
}
public void load(Pack param){
}
}
举个例子
这种模式就好比,dota里面的英雄,分敏捷游戏,力量英雄,智力英雄。就敏捷英雄而言,如果每创造一个英雄就去生成一个新的实例,就会麻烦。但是敏捷英雄有很多共同点比如护甲,攻击力加成方式都是一样的,所以敏捷英雄的内部状态就是他的三维和成才。外部状态就是技能了。
总结
当需要大量使用对象时,减少内存的分配,也能减少垃圾回收器调用的次数。注意在使用的时候,不要无意引入内存泄漏等问题,不然白搞了。
960



被折叠的 条评论
为什么被折叠?



