由遍历集合所联想到的一些问题

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);

// 改进len
t1 = 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并不会占用执行时间。

时间: 2024-10-25 07:55:35

由遍历集合所联想到的一些问题的相关文章

遍历集合,数组和字典

1.字典的遍历 NSDictionary *dict = @{@"name": @"luoguankun",@"address":@"北京"}; //通过for循环遍历NSDictionary,不推荐 NSArray *keys = [dict allKeys]; for (int i = 0; i < keys.count ; i++) { NSString *value = [dict objectForKey:ke

三种形式遍历集合

对于遍历集合获取其对象,在这里总结的三种简单的方式 方式一 : 将集合变为数组,后遍历数组 Object[] obj = list.toArray(); for(Object s : obj){ System.out.println((String) s); } 方式二 :  get()方法获取 . 但只能在list集合中使用, 只有List集合才有索引值. for(int i = 0;i<list.size();i++){ System.out.println(list.get(i)); }

遍历集合的3种方式

import java.util.ArrayList; import java.util.Iterator; /* * ArrayList存储字符串并遍历 * A:迭代器 * B:普通for * C:增强for */ public class ArrayListDemo { public static void main(String[] args) { // 创建集合对象 ArrayList<String> array = new ArrayList<String>(); //

【关于迭代器的for-each遍历集合现象。。。。。】

foreahc迭代集合元素的同时修改集合元素抛异常..ConcurrentModificationException异常 只要使用迭代器遍历,其他集合遍历时进行增删操作都需要留意是否会触发ConcurrentModificationException异常. 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会出现ConcurrentModificationException异常,下面以ArrayList remove 操作进行举例: 使用的数

java8 增强的Iterator遍历集合元素

Iterator接口也是Java集合框架的成员,与Collection和Map两个系列的集合不一样的是Collection和Map系列主要用于充当容器的作用,而Iterator正如其名字一样是主要用于迭代访问Collection集合中的元素,Iterator对象也被称为迭代器. Iterator接口里面定义了下面4个方法: >boolean hasNext():如果被迭代遍历的集合还没有被遍历完,返回True >Object next():返回集合里面的下一个元素 >remove():删

跟王老师学集合(三):使用Iterator接口遍历集合元素

使用Iterator接口遍历集合元素 主讲人:王少华  QQ群号:483773664 学习目标: 1 掌握使用Iterator接口遍历集合元素 一.Iterator概述 Iterator接口主要用于遍历,因此Iterator对象也被称为迭代器.Iterator接口里定义了如下三个方法: boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true Object next():返回集合里下一个元素 void remove():删除集合里上一次next方法返回的元素 二.利用

遍历 集合 数组 增强for Iterator

遍历数组         String[] arr = { "包青天", "白乾涛", "baiqiantao", "bqt", "0909082401", };         for (int i = 0; i < arr.length; i++) {//普通for             System.out.println("第" + (i + 1) + "个元素

java8新特性,使用流遍历集合

在这篇“Java 8新特性教程”系列文章中,我们会深入解释,并通过代码来展示,如何通过流来遍历集合,如何从集合和数组来创建流,以及怎么聚合流的值. 在之前的文章“遍历.过滤.处理集合及使用Lambda表达式增强方法”中,我已经深入解释并演示了通过lambda表达式和方法引用来遍历集合,使用predicate接口来过滤集合,实现接口的默认方法,最后还演示了接口静态方法的实现. 源代码都在我的Github上:可以从 这里克隆. 内容列表 使用流来遍历集合. 从集合或数组创建流. 聚合流中的值. 1.

Java遍历集合的几种方法分析(实现原理、算法性能、适用场合)

概述 Java语言中,提供了一套数据集合框架,其中定义了一些诸如List.Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList. 除此之外,Java对于数据集合的遍历,也提供了几种不同的方式.开发人员必须要清楚的明白每一种遍历方式的特点.适用场合.以及在不同底层实现上的表现.下面就详细分析一下这一块内容. 数据元素是怎样在内存中存放的? 数据元素在内存中,主要有2种存储方式: 1.顺序存储,Random Access(Di