你的位置:首页 > Java教程

[Java教程]由遍历集合所联想到的一些问题



1、以下一段再平常不过的遍历代码,但是与我一样,好多新手都会在这个地方出问题,例如

for(int i=0; i<list.size();i++)  // 执行;

之前我在刚工作的时候在这个地方犯错,我们在自测的时候都没问题。初始化数据之后给客户演示时,突然蹦出个空指针异常NullPointerException。

于是我们通过debug发现,这个list为空,平常我们开发都会手工插入一些数据到表,自然不会报空,演示的时候数据表为空,自然就报错了。
改进:

for(int i=0; list!=null&&i<list.size(); i++)  //执行;

不会报错了吧!

但是有经验的开发人员会发现for循环,每次都会去对循环条件进行判断,于是继续改进

if(list!=null && !list.isEmpty())  for(int i=0; i<list.size(); i++)    // 执行;  

还有问题吗

我们会发现循环还是每次都会去执行list.size();方法,list.size()方法也就是一句代码return size;看似简单的一句代码,但是我们知道每次执行一个方法JVM都会为该方法申请一个栈帧,过程虽短,但是也是一项不小的开销。
于是继续改进

if(list!=null && !list.isEmpty())  for(int i=0,len=list.size(); i<len; i++)    // 执行;  

这样效率真的会高一些吗,实验是最好的答案

List<Integer> list = new ArrayList<Integer>();for(int i=0; i<30000000;i++)list.add(i);// 普通循环long t1 = System.currentTimeMillis();for(int j=0; j<100; j++)  for(int i=0; i<list.size(); i++) {    Integer it1 = list.get(i);  }System.out.println(System.currentTimeMillis()-t1);// 改进lent1 = System.currentTimeMillis();for(int j=0; j<100; j++)  for(int i=0, len=list.size(); i<len; i++) {    Integer it2 = list.get(i);  }System.out.println(System.currentTimeMillis()-t1);// foreach方式遍历t1 = System.currentTimeMillis();for(int j=0; j<100; j++)  for(Integer it4: list) {  }System.out.println(System.currentTimeMillis()-t1);结果普通for结果   22335使用len结果    14369使用foreach结果 29163

经过多次测试发现,普通for结果比len会多出三分之一左右,foreach效果比普通for会略差,而且经过多次测试发现一个有趣的现象,循环位于不同的位置,结果也会有所差距,貌似JVM对循环过的集合会进行优化,下一次再次循环这个集合,速度会有所加快,这个大家也可以去做一下实验,集合过大的时候可能报堆溢出,可以通过修改。-Xms128M -Xmx512M

PS:以上遍历方法不考虑多线程不会有什么问题,如果在多线程下遍历将len提出来,就容易出错了,因为len就获取一次。

2、接下来考虑一个问题,多线程下需要同步遍历某个公共集合,如何实现
我们可能会想到用同步块synchronized

synchronized(list) {   if(list!=null && !list.isEmpty())    for(int i=0,len=list.size(); i<len; i++)      执行;}  

这样的代码真的好吗?我们可以想一想,多线程的情况下,你将公共的list一锁,其他的线程想插入list删除list只能干等着,更严重的是如果你在遍历的时候还执行一些长时间的操作,例如关闭连接或者启动监听等等。
于是我们就想如何能够让等待的时间尽量的短,我们完全可以先将集合克隆下来(至于深浅克隆根据具体情况而论),再去执行,当然如果循环执行时间很短,完全没这个必要。

LifecycleListener[] interested = null;// 用克隆减少时间,因为如果一个一个去触发事件响应,得使用大量时间,而又因为线程锁的存在,使得其他线程必须继续等待synchronized (listeners) {  interested = (LifecycleListener[])listeners.clone();}// 逐个触发事件响应for(int i=0; i<interested.length; i++) {  interested[i].lifecycleEvent(event);}

为什么上面没有使用len中间变量,因为数组的length并不会占用执行时间。