之前在项目中引入Lambda表达式后,最近就把之前的代码改为Lambda表达式,中间遇到了一个小插曲就是List的在调用Stream的forEach()中使用return 、break、continue关键字的问题;加上最近一直关注的“码农每一题”于是自己回顾一下List的基础温故而知新了;
一、List几种遍历方式的问题
List遍历集中方式:
1.loop without size;
2.foreach
3.Iterator
4.Stream.forEach()
5.parallelStream().forEach();
问题1:foreach增强for循环中修改List中element的值操作无效;
示例代码:
public static void main(String[] args) {
int size = 1000;
String s[] = new String[]{"qwqwe", "frsgdf", "asd", "dfsfuytrd", "qwds"};
List<String> asList = Arrays.asList(s);
for (String t : asList) {
if (t.length() <= 4) {
System.out.println(t);
t = "1122";
}
}
for (String tt : asList){
System.out.println("==== :"+tt);
}
}
//程序运行结果
asd
qwds
==== :qwqwe
==== :frsgdf
==== :asd
==== :dfsfuytrd
==== :qwds
问题缘由:
foreach遍历JDK5.0增加的增强for循环,foreach在遍历过程中是通过一个临时变量,记录遍历到的当前List中的element,所以在foreach中操作的对象是指向临时变量的,而不是List中的element实例对象的地址,结果自然就只是修改临时变量的值并没修改List中的element,所以才会出现:foreach增强for循环中修改List中element的值是无效的问题;
解决办法:
改用loop without size实行;
问题2:Iterator迭代时,调用List集合对象remove(Object o)时,抛出Exception;
示例代码:
public static void main(String[] args) {
List<String> asList = new ArrayList<>();
asList.add("qwqwe");
asList.add("frsgdf");
asList.add("asd");
asList.add("dfsfuytrd");
asList.add("qwds");
Iterator<String> iterator = asList.iterator();
String next;
while (iterator.hasNext()) {
next = iterator.next();
if(next.length()<=4){
asList.remove(next);
}
}
}
//运行结果
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at Main.main(Main.java:31)
问题缘由:
这个问题是和Iterator的实现方式有关系的,以ArrayList为例,在ArrayList中.iterator()其实是通过工厂模式在内部new出来一个Iterator对象,而且这个
Iterator对象的size是按照它被创建时List的.size()大小创建的,如果在iterator()中调用List的remove方法,这就会导致Iterator的size大于List的size,进而发生IndexOutOfBoundsException越界异常
(List中改为抛出ConcurrentModificationException,可参考ArrayList.Itr.next()函数);
解决办法:
1.如果list中需要删除一个element的操作的话可以的话,删除完成直接break;这样也可以节约时间和减小性能开销;
2.调用Iterator的remove()方法进行删除【在源码中可以看到在Iterator的remove()中同时也调用了List的remove(),这保持了List的size和Iterator的size一致,避免出现越界异常;】
问题3:JDK8中Stream.forEach()遍历时return、break、continue关键字使用【parallelStream也存在这样问题】;
在JDK8中引入的Stream中利用forEach()遍历List中,发现break和continue两个关键字IDE会直接提示语法错误的,所以这连个关键字就直接可以pass了,直接看return吧;
示例代码:
public static void main(String[] args) {
List<String> list = Arrays.asList("qwqwe", "frsgdf", "asd", "dfsfuytrd", "qwds");
list.stream().forEach(e -> {
if (e.length() <= 4) {
System.out.println("----"+e);
return;
}
System.out.println(e);
});
}
//程序执行结果:
qwqwe
frsgdf
----asd
dfsfuytrd
----qwds
问题缘由:
在stream[parallelStream中也是一样的]中关键字return、break、continue关键字使用问题是和Java8中流Stream的设计有关系的,在Java8中引入的流的目的是提高并发执行效率即:Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation);
可以理解为Stream操作的对象是Collection)集合对象自身而不是集合Collection中的element,而且Stream中的各个方法都是一旦开始执行就没有回头路,只能等待全部执行完成。
1.break和continue关键字在Sream中失效问题,个人理解为:Stream每次执行的是对整个集合为最小操作单元,而break和continue是以集合Collection中的element为操作单元的,所以这两关键字在设计上就不是一个量级的,所以它们在Stream面前就失效了;
2 .return 在遍历结果来看其实充当了continue的角色,同样return在整个Java中的方法中充当了“急刹车和掉头返回”的功能,根据上面的理解:在上述代码中return的对象只是Steam操作中的一个小支流element,所以return也只能对其中目标element进行刹车,并不能阻止其他element的继续,结果就是return在Stream中充当continue成了一个既成事实;
解决办法:
个人的观点是跟着党走有酒有肉,搞清楚缘由了,根据实际需求灵活选择;
小插曲
刚开始看结果给人的感觉就是return充当了continue的角色,而且还是按照List的顺序执行的,菜鸡还是百度了一下结果都说Java8中的stream是并发的数据量大的话就可能是出现乱序,于是赶紧自己测试了1000个String结果任然是按顺序打印的,又在CSDN中看到有人说String太简单,于是new了一个JavaBean结果还是按照顺序打印,于是越发感觉网上"那些人是胡说",再者在源码看到stream和parallelStream二者差别时,更加确认stream是sequential有序的,而parallelStream是parallel无序的;
二、List几种遍历方式的效率问题
Java一直被人诟病的就是效率问题了,所以最后咋能不简单的对比一下呢;
- -
基础测试前准备问题
1.经验告诉我们是性能越差劲的设置越能放大代码效率差异,所以选择Android手机测试,况且自己就搞Android开发的
2.测试选择ArrayList和LinkedList两种最长用到的List,同时也是两种不同数据结构的List进行验证;
3.测试Size分别选择size为50,500,1000,5000,10000,50000作为验证变量;
4.测试List遍历的对象为JavaBean【有String.int long三种基本类型,且每次遍历都是相同打印操作】;
5.测试过程中所有的遍历方式中操作完全相同;
6.测试过程中每次测试前杀死手机其他app,完成一次测试后杀死测试app等一小会尽可能消除内存影响;
测试结果为:
基本结论:
1.随着Size的逐渐变大parallelStream遍历的效率就越明显,在Size达到5000+以后parallelStream遍历时间基本上是其他遍历方式的时间的一半 ;
2.根据测试结果,在JDK8之前几种遍历的方式中通过Size循环遍历效率最差,Iterator和foreach效率基本差不多,但是foreach代码更简洁;
3.在parallelStream遍历中LinkedList的遍历效率明显优于ArrayList;这是和LinkedList的数据结构以及parallelStream的遍历逻辑有关系的
4.JDK8中引入是stream在List的size在5000以下时遍历的时间由于其他遍历方式【parallelStream以外】这个结果不知道正确不;
测试的几个问题:
1.在测试过程中发现同样的Size测试几次结果几乎每次都有细微的差异,个人分析认为是和测试时间手机状态有关系,不同时间手机系统内部不同操作导致CPU占用情况不相同导致的;
2.这个测试数据结果中并没有很明显体现出ArrayList和LinkedList相比在查询的中的优势:在foreach遍历方式中二者时间基本上没有差异;这个有点不太明白是什么原因导致的,希望有哪位大佬答疑解惑。
三、附上所有测试代码和手机参数
测试Android手机:华为畅享7(SLA-AL00/2GB RAM/全网通);
测试完整代码:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.button_a)
Button buttonA;
private static int size = 1000;
@BindView(R.id.size)
EditText dt_size;
@BindView(R.id.button_b)
Button buttonB;
@BindView(R.id.textView)
TextView textView;
private ArrayList<TestBean> as;
private LinkedList<TestBean> ls;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@SuppressLint("NewApi")
@OnClick({R.id.button_a, R.id.button_b})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.button_a:
initData();
break;
case R.id.button_b:
test();
break;
}
}
private void initData() {
size = Integer.parseInt(dt_size.getText().toString());
Log.e("TAG", "initData: ======= > SIZE :" + size);
as = null;
ls = null;
as = new ArrayList<>(size);
ls = new LinkedList<TestBean>();
for (int i = 0; i < size; i++) {
this.as.add(new TestBean("AS index :" + i, System.currentTimeMillis(), i));
this.ls.add(new TestBean("LS index :" + i, System.currentTimeMillis(), i));
}
Log.e("TAG", "initData: ======= > init date over");
}
@SuppressLint("NewApi")
private void test() {
StringBuilder sb = new StringBuilder();
/*------------------------------loop without size------------------------------*/
long l1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
Log.i("TAG", "AS: ---- > " + as.get(i).toString());
}
long l = System.currentTimeMillis() - l1;
sb.append("AS loop without :"+l).append(" _ ");
long l2 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
Log.i("TAG", "LS: ---- > " + ls.get(i).toString());
}
long ll = (System.currentTimeMillis() - l2);
sb.append("LS loop without :"+ll).append(" _ ");
/*-----------------------------------foreach-------------------------*/
long l3 = System.currentTimeMillis();
for (TestBean bean : as) {
Log.i("TAG", "AS: ---- > " + bean.toString());
}
long l_3 = (System.currentTimeMillis() - l3);
sb.append("AS foreach :"+l_3).append(" _ ");
long l4 = System.currentTimeMillis();
for (TestBean bean : ls) {
Log.i("TAG", "LS: ---- > " + bean.toString());
}
long l_4 = (System.currentTimeMillis() - l4);
sb.append("LS foreach :"+l_4).append(" _ ");
/*-------------------------------Iterator-----------------------------*/
long l5 = System.currentTimeMillis();
Iterator<TestBean> iterator = as.iterator();
while (iterator.hasNext()) {
Log.i("TAG", "AS: ---- > " + iterator.next().toString());
}
long l_5 = (System.currentTimeMillis() - l5);
sb.append("AS Iterator :"+l_5).append(" _ ");
long l6 = System.currentTimeMillis();
Iterator<TestBean> Literators = ls.iterator();
while (Literators.hasNext()) {
Log.i("TAG", "LS: ---- > " + Literators.next().toString());
}
long l_6 = (System.currentTimeMillis() - l6);
sb.append("LS Iterator :"+l_6).append(" _ ");
/*----------------------------------stream--------------------------*/
long l7 = System.currentTimeMillis();
as.stream().forEach(e -> Log.i("TAG", "AS: ---- > " + e.toString()));
long l_7 = (System.currentTimeMillis() - l7);
sb.append("AS stream :"+l_7).append(" _ ");
long l8 = System.currentTimeMillis();
ls.stream().forEach(e -> Log.i("TAG", "LS: ---- > " + e.toString()));
long l_8 = (System.currentTimeMillis() - l8);
sb.append("LS stream :"+l_8).append(" _ ");
/*---------------------------------parallelStream---------------------------*/
long l9 = System.currentTimeMillis();
as.parallelStream().forEach(e -> Log.i("TAG", "AS: ---- > " + e.toString()));
long l_9 = (System.currentTimeMillis() - l9);
sb.append("AS parallelStream :"+l_9).append(" _ ");
long l10 = System.currentTimeMillis();
ls.parallelStream().forEach(e -> Log.i("TAG", "LS: ---- > " + e.toString()));
long l_10 = (System.currentTimeMillis() - l10);
sb.append("LS parallelStream :"+l_10).append(" _ ");
textView.setText(sb.toString());
Log.e("TAG", "-------------------------------TEST OVER----------------------------------");
Log.e("TAG", "---------------- RESULT : "+sb.toString());
}
class TestBean {
private String str;
private long time;
private int index;
public TestBean(String str, long time, int index) {
this.str = str;
this.time = time;
this.index = index;
}
@Override
public String toString() {
return "TestBean{" + "str='" + str + '\'' + ", time=" + time + ", index=" + index + '}';
}
}
}
测试主页View