比如最简单的 List 遍历,会有这样的写法:
for
(
int
i=
0
;i
同样是对 List 的操作,如果要在遍历同时进行增加和删除操作,代码如下:
for
(
int
i=
0
,j=l.size();i=
0
;i--){l.remove(i);}。经过测试,如果采用 ArrayList,两种写法在循环次数较
少时没有太大的区别,循环次数为
1000
,均为 1ms 以内,次数为
10000
,前一种为 60ms
左右,后一种为 1ms 以内,,而次数上到
100000
,前一种为 6000ms 左右,后一种为
15ms,随着循环次数的增多,后一种较前一种的效率优势明显提高。
这是由 Collection 库 ArrayList 的实现决定的,以下是 jdk1.
3
的 ArrayList 源码:
public
Object remove(
int
index)
{
RangeCheck(index);
modCount++;
Object oldValue = elementData[index];
int
numMoved = size - index -
1
;
if
(numMoved >
0
)
System.arraycopy(elementData, index+
1
, elementData, index,numMoved);
elementData[--size] =
null
;
// Let gc do its workreturn oldValue;
}
>
}
从中我们可以看出,numMoved 代表了需要进行 arraycopy 操作的数量,它是由
remove 的位置决定的,如果 index=
0
,也就是删除第一个元素,则需要 arraycopy 后面的
所有数据,而如果 index=size-
1
,则只需将最后一个元素设为
null
即可。所以从后面向前
循环 remove 是比较好的写法。
如果 List 中的确存在较多的 add 或 remove 操作,且容量较大(如存储几万个对象),
则应该采用 LinkedList 作为实现。LinkedList 内部采用双向链表作为数据结构,比
ArrayList 占用较多内存空间,且随机访问操作较慢(需要从头或尾循环到相应位置),但
插入删除操作很快(仅需进行链表操作,无须大量移动或拷贝)。
对于 List 操作如果循环规模较小,其实对性能影响非常小(ms 级),远远不是性能瓶
颈所在。但心中有着优化的意识,并力求写出简洁高效的程序应该是我们每个程序员的追
求。而且一旦在循环规模较大时,如果有了这些意识,也就能有效的消除性能隐患。
再举一个与优化无关但确实可能成为性能杀手(可以说是 bug)的循环的例子。下面是
源代码:
for
(; totalRead < m_totalBytes; totalRead += readBytes)
{
readBytes = m_request.getInputStream().read(m_binArray, totalRead, m_totalBytes - totalRead);
}
这个代码意图很清楚,就是将一个 InputStream 流读到一个
byte
数组中去。它使用
read 方法循环读取 InputStream,该方法返回读取的字节数。正常情况下,该循环运行良好,
当 totalRead=m_totalBytes 时,结束循环,
byte
数组被正常填充。但如果仔细看一下
InputStream 的 read 方法的说明,了解一下其返回值就会发现,返回值可能为-
1
,即已读