我叫岁寒,一名输入法开发者。下面介绍的是我在输入法开发中使用的一个优化方法——高效的对象循环利用。**(之所以用临岁之寒,是因为有人捷足先登,我更希望你们叫我岁寒,和我的输入法同名)
输入法是常用的软件工具,其对时间响应速度有较高的要求,须要专门优化。与输入法响应速度相关的因素有很多,比如数据库检索、多线程调度和对象管理等方面。本文主要从对象管理方面入手进行优化。
优化思路
我开发的输入法是在安卓平台上运行,使用JAVA语言开发。由于JAVA自带垃圾回收机制,所以很多时候程序员并不需要关心对象如何被回收利用。这种机制有利也有弊,弊在生成和回收对象时程序都要付出时间代价。想要避免这种时间代价,就要绕过回收机制,重复利用已生成的对象,从而减少对象的生成和回收操作。
使用场景
脱离具体场景谈优化都是耍流氓,很少有放之四海而皆准的优化方法,我们先说一说这个优化方法的使用场景。
1.该方法适用于那些方生方死的对象,即我们只是短时间内需要这些对象,很快就弃用,但又需要不断地生成新的对象来使用。 2.我们可以估算出同一时间内有效对象的最大数量。这一点很重要,否则我们并没有办法循环利用对象。
3.多线程竞争烈度较低,或者可以容忍一定的出错概率。
其它需求
根据我项目的需要,我还希望这些对象可以在输入法键盘不使用的时候还给系统,同时,在用户再次调出输入法的时候又可以自动生成,并且使用的时候代码量越少越好。
具体实现
下述的所有代码都在一个叫ObjectBuffer<T>的类里面,先看一下类中的成员。
<pre> <code>
//用于保存所有该类的对象,实现内存的统一回收
static List<ObjectBuffer> AllObjectBuffer = new ArrayList<>();
final int objectNumber;
private final Class objectClass;
private Object[] ObjectArray = null;//保存对象的循环数组
private volatile int index = 0;
</code></pre>
下面是构造器函数和初始化方法。
构造器参数是要复用的类的Class对象和循环数组的长度。
初始化方法则利用反射生成对象备用。使用反射可以节省大量的重复代码。这个类虽然使用了泛型,但在JAVA中并不能使用泛型生成对象,所以此处泛型的作用基本上只是减少了外部执行类型转换的操作而已。
<pre> <code>
public ObjectBuffer(Class objectClass, int number) {
this.objectClass = objectClass;
this.objectNumber = number;
AllObjectBuffer.add(this);
}
</p>
private void initObjectArray() {
index=0;
ObjectArray = new Object[objectNumber];
try {
Constructor constructor = this.objectClass.getConstructor((Class[]) null);
for (int i = 0; i < ObjectArray.length; i++) {
ObjectArray[i] = constructor.newInstance((Object[]) null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
</code></pre>
下面是这个类的核心方法。利用加一和取余让索引循环起来,这个不多说。在本方法中,有两个明显可能出现的异常:空引用和越界,这里使用了异常处理。
当然,对空引用也可以用null条件判断,对越界也可以用边界条件判断。但这样会增加很多代码,而且每一次取对象都要做两次判断,这大多时候都是浪费时间。利用异常可以省下这两个判断,代码也更显洗练。
其中,index被声明为volatile,可使其在多线程中具有更好的可见性,但这并不是绝对线程安全的,在烈度较大的多线程竞争下应该增加同步锁。但在高烈度竞争下的同步操作可能抵冲掉循环复用对象的优化效果,因此要谨慎使用。
<pre><code>
public T Obtain() {
try {
return (T) ObjectArray[index++%objectNumber];
} catch (Exception e) {
initObjectArray();
return Obtain();
}
} </code></pre>
下面的代码用于在用户不使用键盘时释放内存空间。显然,当用户再一次使用输入法,程序试图获取对象时会触发NULL异常,随后调用异常处理,从而实现对象的重新初始化。
可见,异常处理不仅只是作为异常情况的补救措施,也可以作为程序逻辑的一部分。但此做法的前提是在异常发生频率很低的情况下,否则就可能得不偿失。
<pre><code>
public static void ClearAllObjectBuffer() {
for (ObjectBuffer objectBuffer : AllObjectBuffer) {
objectBuffer.clear();
}
}
</p>
private void clear() {
ObjectArray = null;
}
</code></pre>
使用方法
<pre><code>
public class A{
ObjectBuffer buffer =new ObjectBuffer(A.Class,10);
public A Obtain(){
A a =buffer.Obtain();
//这里可以对a做一些清理工作
return a;
}
}
</code></pre>
注意事项
千万不要对需要长期持有的对象使用该方法。切记切记。
写在最后
这个类的代码量其实很少,你如果想要使用这个类,只要把文中的代码整理起来就可以用了。既然代码这么少,我为什么不直接啪代码,而是唧唧歪歪地写这么多东西呢?因为我认为,我是怎么做的不重要,我为什么这么做才是最重要的,所谓道重于术。
网友评论