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的一个设计模式,主要目的就是屏蔽不同数据集合的特点,统一遍历集合的接口。从结构上可以看出,迭代器模式在客户与容器之间加入了迭代器角色。迭代器角色的加入,就可以很好的避免容器内部细节的暴露,而且也使得设计符号“单一职责原则”;
每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取代了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现都是需要保存当前遍历的位置,然后根据当前位置来向前或者向后移动指针

迭代器模式的写法如下:

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

3)foreach循环遍历
foreach内部也是采用了Iterator的方式实现,根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码
优点:代码简洁,不易出错
缺点:只能做简单的遍历,不能在遍历过程中操作(删除、替换)数据集合

for (ElementType element : list) {
}

遍历性能

1)传统的for循环遍历基于计数器
ArrayList按位置读取的代码:直接按元素位置读取

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}

LinkedList按位置读取的代码:如果index小于容量的一半,则从头开始查找,否则从尾部开始查找

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

Node<E> node(int index) {
    // assert isElementIndex(index);
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

2)迭代器遍历Iterator
对于RandomAccess类型的集合来说,如ArrayList迭代器遍历反而因为一些额外的操作,还会增加额外的运行时间;
对于Sequential Access的集合来说,因为Iterator内部维护了当前遍历的位置,所以每次遍历,读取下一个位置并不需要从集合的第一个元素开始查找,只要把指针向后移一位就行了,这样一来遍历整个集合的时间复杂度就降低为O(n);以LinkedList的迭代器为例,其内部实现,就是维护当前遍历的位置,然后操作指针移动就可以了

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

使用场景

1)传统的for循环遍历基于计数器
适用于遍历顺序存储集合,读取性能比较高如ArrayList;不适用于遍历链式存储的集合如LinkedList,时间复杂度太大;

2)迭代器遍历Iterator
对于顺序存储的数据结构,如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。
链式存储:推荐此种遍历方式,平均时间复杂度降为O(n)

3)foreach循环遍历
foreach让代码更加简洁,缺点就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,性能上相差不大

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值