概述

Java语言中,提供了一套数据集合框架,其中定义了一些诸如List、Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList。

除此之外,Java对于数据集合的遍历,也提供了几种不同的方式。开发人员必须要清楚的明白每一种遍历方式的特点、适用场合、以及在不同底层实现上的表现。下面就详细分析一下这一块内容。

数据元素是怎样在内存中存放的?

数据元素在内存中,主要有2种存储方式:

1、顺序存储,Random Access(Direct Access):

这种方式,相邻的数据元素存放于相邻的内存地址中,整块内存地址是连续的。可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1)。正常来说,只有基于数组实现的集合,才有这种特性。Java中以ArrayList为代表。

2、链式存储,Sequential Access:

这种方式,每一个数据元素,在内存中都不要求处于相邻的位置,每个数据元素包含它下一个元素的内存地址。不可以根据元素的位置直接计算出内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n)。主要以链表为代表。Java中以LinkedList为代表。

Java中提供的遍历方式有哪些?

1、传统的for循环遍历,基于计数器的:

遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。这也是最原始的集合遍历方法。

写法为:

for (int i = 0; i < list.size(); i++) {

list.get(i);

}

2、迭代器遍历,Iterator:

Iterator本来是OO的一个设计模式,主要目的就是屏蔽不同数据集合的特点,统一遍历集合的接口。Java作为一个OO语言,自然也在Collections中支持了Iterator模式。

写法为:

Iterator iterator =list.iterator();while(iterator.hasNext()) {

iterator.next();

}

3、foreach循环遍历:

屏蔽了显式声明的Iterator和计数器。

优点:代码简洁,不易出错。

缺点:只能做简单的遍历,不能在遍历过程中操作(删除、替换)数据集合。

写法为:

for(ElementType element : list) {

}

每个遍历方法的实现原理是什么?

1、传统的for循环遍历,基于计数器的:

遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。

2、迭代器遍历,Iterator:

每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取缔了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。

3、foreach循环遍历:

根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码。

各遍历方式对于不同的存储方式,性能如何?

1、传统的for循环遍历,基于计数器的:

因为是基于元素的位置,按位置读取。所以我们可以知道,对于顺序存储,因为读取特定位置元素的平均时间复杂度是O(1),所以遍历整个集合的平均时间复杂度为O(n)。而对于链式存储,因为读取特定位置元素的平均时间复杂度是O(n),所以遍历整个集合的平均时间复杂度为O(n2)(n的平方)。

ArrayList按位置读取的代码:直接按元素位置读取。

transientObject[] elementData;public E get(intindex) {

rangeCheck(index);returnelementData(index);

}

E elementData(intindex) {return(E) elementData[index];

}

LinkedList按位置读取的代码:每次都需要从第0个元素开始向后读取。其实它内部也做了小小的优化。

transient int size = 0;transient Nodefirst;transient Nodelast;public E get(intindex) {

checkElementIndex(index);returnnode(index).item;

}

Node node(intindex) {if (index < (size >> 1)) { //查询位置在链表前半部分,从链表头开始查找

Node x =first;for (int i = 0; i < index; i++)

x=x.next;returnx;

}else { //查询位置在链表后半部分,从链表尾开始查找

Node x =last;for (int i = size - 1; i > index; i--)

x=x.prev;returnx;

}

}

2、迭代器遍历,Iterator:

那么对于RandomAccess类型的集合来说,没有太多意义,反而因为一些额外的操作,还会增加额外的运行时间。但是对于Sequential

Access的集合来说,就有很重大的意义了,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来,遍历整个集合的时间复杂度就降低为O(n);

(这里只用LinkedList做例子)LinkedList的迭代器,内部实现,就是维护当前遍历的位置,然后操作指针移动就可以了:

代码:

publicE next() {

checkForComodification();if (!hasNext())throw newNoSuchElementException();

lastReturned=next;

next=next.next;

nextIndex++;returnlastReturned.item;

}publicE previous() {

checkForComodification();if (!hasPrevious())throw newNoSuchElementException();

lastReturned= next = (next == null) ?last : next.prev;

nextIndex--;returnlastReturned.item;

}

3、foreach循环遍历:

分析Java字节码可知,foreach内部实现原理,也是通过Iterator实现的,只不过这个Iterator是Java编译器帮我们生成的,所以我们不需要再手动去编写。但是因为每次都要做类型转换检查,所以花费的时间比Iterator略长。时间复杂度和Iterator一样。

使用Iterator的字节码:

Code:0: new #16 //class java/util/ArrayList

3: dup4: invokespecial #18 //Method java/util/ArrayList."":()V

7: astore_18: aload_19: invokeinterface #19, 1 //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;

14: astore_215: goto 25

18: aload_219: invokeinterface #25, 1 //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;

24: pop25: aload_226: invokeinterface #31, 1 //InterfaceMethod java/util/Iterator.hasNext:()Z

31: ifne 18

34: return

使用foreach的字节码:

Code:0: new #16 //class java/util/ArrayList

3: dup4: invokespecial #18 //Method java/util/ArrayList."":()V

7: astore_18: aload_19: invokeinterface #19, 1 //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;

14: astore_315: goto 28

18: aload_319: invokeinterface #25, 1 //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;

24: checkcast #31 //class loop/Model

27: astore_228: aload_329: invokeinterface #33, 1 //InterfaceMethod java/util/Iterator.hasNext:()Z

34: ifne 18

37: return

各遍历方式的适用于什么场合?

1、传统的for循环遍历,基于计数器的:

顺序存储:读取性能比较高。适用于遍历顺序存储集合。

链式存储:时间复杂度太大,不适用于遍历链式存储的集合。

2、迭代器遍历,Iterator:

顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。

链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。

3、foreach循环遍历:

foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。

Java的最佳实践是什么?

Java数据集合框架中,提供了一个RandomAccess接口,该接口没有方法,只是一个标记。通常被List接口的实现使用,用来标记该List的实现是否支持Random

Access。

一个数据集合实现了该接口,就意味着它支持Random

Access,按位置读取元素的平均时间复杂度为O(1)。比如ArrayList。

而没有实现该接口的,就表示不支持Random Access。比如LinkedList。

所以看来JDK开发者也是注意到这个问题的,那么推荐的做法就是,如果想要遍历一个List,那么先判断是否支持Random

Access,也就是 list instanceof RandomAccess。

比如:

if (list instanceofRandomAccess) {//使用传统的for循环遍历。

} else{//使用Iterator或者foreach。

}

java遍历是什么意思_Java遍历集合的几种方法分析(实现原理、算法性能、适用场合)...相关推荐

  1. java遍历文件和归类_java读取文件的两种方法:java.io和java.lang.ClassLoader

    java读取文件的两种方法:java.io和java.lang.ClassLoader 什么时候使用java.io,什么时候使用java.lang.ClassLoader呢? (注:要是之前读xml文 ...

  2. java加载xml配置文件_java读取配置文件的几种方法

    原标题:java读取配置文件的几种方法 在现实工作中,我们常常需要保存一些系统配置信息,大家一般都会选择配置文件来完成,本文根据笔者工作中用到的读取配置文件的方法小小总结一下,主要叙述的是spring ...

  3. java的字符串截取函数_java 字符串截取的三种方法(推荐)|chu

    众所周知,java提供了很多字符串截取的方式.下面就来看看大致有几种. 1.split()+正则表达式来进行截取. 将正则传入split().返回的是一个字符串数组类型.不过通过这种方式截取会有很大的 ...

  4. java中如何读写文件_JAVA: 读写文件的几种方法

    如果您使用java8,可以也参考这篇文章:JAVA: Java8流逐行读取文件 import java.io.BufferedReader; import java.io.BufferedWriter ...

  5. java for循环创建线程_Java创建线程的两种方法

    大多数情况,通过实例化一个Thread对象来创建一个线程.Java定义了两种方式: 实现Runnable 接口: 可以继承Thread类. 下面的两小节依次介绍了每一种方式. 实现Runnable接口 ...

  6. java 获取整型随机数_java获取随机数的几种方法

    1.Math.random()方法 例子:求1到10之间的随机数 public static void main(String[] args) { int n; for (int i = 0; i & ...

  7. java遍历是什么意思_Java遍历集合方法分析(实现原理、算法性能、适用场合)...

    概述 Java语言中,提供了一套数据集合框架,其中定义了一些诸如List.Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList ...

  8. Java中遍历Set集合的三种方法

    Map集合:链接: Map集合的五种遍历方式及Treemap方法 Set集合:链接: Java中遍历Set集合的三种方法 TreeSet集合:链接: Java深入了解TreeSet,和迭代器遍历方法 ...

  9. java_基础_遍历map删除元素_Java 遍历Map(包括集合)时,修改删除元素

    转载自:https://blog.csdn.net/weixin_33498283/article/details/114071025 1.遍历Map集合的四种方法 public staticvoid ...

最新文章

  1. Oracle中用For Loop 替代Cursor
  2. 第二次作业 讲解及展示
  3. 计算机启用网络查找,怎么搜索局域网中的电脑
  4. MingW环境下的windows编程
  5. 基于直接最小二乘的椭圆拟合(Direct Least Squares Fitting of Ellipses)
  6. (Unity4.7)assetbundle 坑爹总结
  7. H264 帧、pps 、sps
  8. saltstack之nginx、php的配置
  9. java 仓库管理_Java仓库管理系统(一)
  10. Qt QPainter绘图
  11. 拼音加加符号编码列表
  12. java 返回类对象_JAVA如何实现返回不同类型的对象
  13. 有趣的python小程序
  14. photoshop给照片去斑的一些办法
  15. python豆瓣电影top250爬虫课程设计_[教程]图文:爬虫爬取豆瓣电影top250
  16. 卸载 x 雷某度!GitHub 标星 1.5w+,从此我只用这款全能高速下载工具Motrix!
  17. java 翻译接口_java版本 百度翻译接口
  18. 计算机科学与技术 转
  19. 切割字符串长度php,C++_C语言中计算字符串长度与分割字符串的方法,C语言strlen()函数:返回字符串 - phpStudy...
  20. keil软件调试(Debug)仿真教程(软件调试和硬件调试的区别)及常用调试按键详解

热门文章

  1. galaxy+note8+android+8.0,太遗憾了!三星Galaxy S8和Note8无缘安卓10更新
  2. Tomcat 部署配置问题
  3. 2009.12--FSMO概念
  4. 怎样获取百度网盘下载链接
  5. 构建自主创新产业生态实现服务器关键技术超越
  6. python3 判断偶数/奇数
  7. PreferenceFragment 使用 小结
  8. Linux系统服务详解
  9. AlertDialog弹出对话框
  10. 如何简单实现接口自动化测试(基于 python)