在上一篇中提出的原则都是为了避免创建不必要的对象。对于占用资源较多的对象,可以在程序初始化中创建,并一直使用,比如图片资源。对于一些不变的对象,如字符串、常数,可以采用静态常量的办法。但我们开发过程中还会遇到另一种情况,有些对象虽然占用资源少,但使用频率高(比如记录屏幕点位置坐标的对象),对于这种情况应该如何处理呢?
这个问题其实比较复杂,要具体情况具体分析,但还是有一些规律可循。
原则1:建立对象池循环利用小对象
例如如下类:
1
public
class
TestPoint
2
{
3
public
int
mX;
4
public
int
mY;
5
public
TestPoint(
int
x,
int
y)
6
{
7
set(x,y);
8
}
9
10
public
void
set(
int
x,
int
y)
11
{
12
mX
=
x;
13
mY
=
y;
14
}
15
}
使用时:
public
void
test()
{
for
(
int
i
=
0
;i
<
10000
;i
++
)
{
TestPoint p
=
new
TestPoint(i,i);
。。。
}
}
通过分析,以上代码会创建10000个TestPoint新对象,这当然是不合理的,可以用如下办法优化:
1
public
class
TestPoint
2
{
3
public
int
mX;
4
public
int
mY;
5
public
TestPoint(
int
x,
int
y)
6
{
7
set(x,y);
8
}
9
10
public
void
set(
int
x,
int
y)
11
{
12
mX
=
x;
13
mY
=
y;
14
}
15
16
17
/**
18
* 对象池
19
*/
20
private
static
ArrayList
<
TestPoint
>
sPoints
=
null
;
21
22
/**
23
* 从对象池中获取一个对象,如果空池则创建新对象,并初始化
24
*
@param
x 横坐标
25
*
@param
y 纵坐标
26
*
@return
对象
27
*/
28
public
static
TestPoint popPoint(
int
x,
int
y)
29
{
30
TestPoint p
=
null
;
31
if
(sPoints
!=
null
&&
sPoints.size()
>
0
)
32
{
33
p
=
sPoints.remove(
0
);
34
p.set(x, y);
35
}
36
else
37
{
38
p
=
new
TestPoint(x,y);
39
}
40
return
p;
41
}
42
43
/**
44
* 向对象池归还一个对象,如果空池则先创建池
45
*
@param
p 要归还的对象
46
*/
47
public
static
void
pushPoint(TestPoint p)
48
{
49
if
(sPoints
==
null
)
50
{
51
sPoints
=
new
ArrayList
<
TestPoint
>
();
52
}
53
54
sPoints.add(p);
55
}
56
57
}
在使用时:
public
void
test2()
{
for
(
int
i
=
0
;i
<
10000
;i
++
)
{
TestPoint p
=
TestPoint.popPoint(i, i);
//
获取对象
。。。
TestPoint.pushPoint(p);
//
归还对象
}
}
这样,由于使用过的对象归还给了对象池,当需要时优先从池中获取对象,实际创建对象的次数将明显下降。
原则2:给你的对象池设一个上限
前面的代码也有局限性,例如如果程序的逻辑造成其对象获取峰值较高,池中的对象将或很多,当程序对象获取需求减少时,池中的对象就成为了占用内存的累赘了。一个可行的办法是,为对象池中对象数设定一个上限,当归还对象时发现池中对象已经达到这个上限了,就不再将其放入池中。只要这个上限设定合理,池内存占用和GC释放内存压力可以找到一个较好的平衡点。
原则3:保护好你的对象池
这里需要强调的是,对于多线程共用的对象池,应该对所有访问对象池的静态方法进行严格的线程保护,因为ArrayList的操作还是比较费时且多线程共同对其访问是是有线程冲突的。
小结:
对象池的使用可以对频繁使用的小对象进行有效的循环利用,如果运用合理,可以极大地提升程序的运行效率和降低程序的资源占用。但请相信,没有普遍适用的最优方案,必须具体问题具体分析。
作者:汪峰 www.otlive.cn