Java集合遍历方式及效率对比

一、List集合遍历

1. 集合元素在内存中的存放

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

  • 顺序存储,Random Access(或直接存储,Direct Access):
    这种方式,相邻的数据元素存放于相邻的内存地址中,整块内存地址是连续的。可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1)。这种数据结构插入和删除时比较麻烦,查询比较方便。正常来说,只有基于数组实现的集合,才有这种特性。Java中以ArrayList为代表;

  • 链式存储,Sequential Access:
    这种方式,数据元素放在独立的空间中,在内存中每个元素的内存地址都不要求是相邻的。所以每个元素中需要保存下一个元素的索引,即下一个元素的内存地址。所以这种数据结构插入和删除比较方便,但是查找很麻烦,要从第一个开始遍历,因为它无法根据元素的位置直接计算出内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n)。主要以链表为代表。Java中以LinkedList为代表。

2. 遍历的4种方式list集合遍历的4种方式
3. 遍历结果
list集合遍历结果

4. 不同的List接口实现使用不同的遍历方式

首先譬如 ArrayList 是直接通过数组来进行存储,所以在使用下标的方式循环遍历的时候性能最好,通过下标可以直接取数据,速度最快,而且由于 ArrayList 是实现了 RandomAccess 接口(随机存取),这个接口是一个标记接口,表明了 ArrayList 集合的数据元素之间没有关联,位置间没有索引依赖。而如果对于 ArrayList 使用 for-each 或者迭代器进行遍历就没有 index 索引遍历效率高了,因为迭代器强制将 RandomAccess 的 ArrayList 建立了前后遍历关系,且在每次遍历过程中进行了一堆判断,所以相对来说对于 ArrayList 来说遍历使用普通 index 比迭代器要效率高些,但是差距不是十分明显。
  接着再拿 LinkedList 来说,其为双向链表的实现存储,前后元素是通过链表索引建立关联的,无法直接取到对应的下标,因此在使用普通的 index 索引下标遍历时就需要计算对应的元素在哪,二分法决定头部还是尾部遍历,然后一步步的遍历找到元素,所以在遍历中每次都要从头查找元素位置,十分低效率(使用下标需要重复判断,再一步一步遍历,所以比较慢)。而迭代器的实现就是指向下一个元素,迭代器直接通过 LinkedList 的指针进行遍历,一次遍历就能找到每个合适的元素,所以 LinkedList 在使用迭代器遍历时效率最高。
  总之就是对于不同数据结构实现的集合列表就应该选择不同的遍历方式,尤其是 LinkedList 一定不要使用普通索引下标方式遍历,其效率极低。


5. 使用场景

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

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

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

  • Java 集合 List 遍历性能相关问题


二、Map集合遍历

1. Map存储

Map内以 键/值对 形式来存储数据,通过 “键” 对象来查询 “值” 对象,其中key值是唯一的(不能重复),而value对象是与key对象关联在一起的,Map接口有两个实现:

  • HashMap,HashMap基于哈希表实现,key/value对是按照Hash算法存储的。HashMap可以通过调优初始容量和负载因子,优化HashMap空间的使用;
    TreeMap,TreeMap基于树实现,key/value对是排序(按key排序)存储的。TreeMap没有调优选项,因为该树总处于平衡状态。

HashMap的性能优于TreeMap。


2. 遍历的4种方式

  • 使用keySet()方法遍历:将Map中所有的键存入到set集合中,通过对Set集合的遍历,然后使用map.get(key)方法取出value值;这种方式取到的结果会乱序,因为HashMap.keySet()方法返回的Set结果,里面的数据是乱序排放的;

  • 使用Map的entrySet()方法返回一个以Entry为元素的Set集合,然后对Set集合进行遍历;Map.Entry表示映射关系,一个关系就是一个键值对,entrySet()迭代后可以通过getKey(),getValue()两种方法来取得key和value。

Map集合四种遍历方式
如果仅需要获取到value值,还可以用

Iterator<People> iterator = map.values().iterator();  
while (iterator.hasNext()) {  
	value = iterator.next();  
}

3. 遍历结果
Map集合遍历结果

4. 使用场景

1)如果使用HashMap:

  • 同时遍历key和value时,keySet与entrySet方法的性能差异取决于key的具体情况,如复杂度(复杂对象)、离散度、冲突率等。换言之,取决于HashMap查找value的开销。entrySet一次性取出所有 key和value的操作是有性能开销的,当这个损失小于HashMap查找value的开销时,entrySet的性能优势就会体现出来。如果大于,比如当key是最简单的数值字符串时,keySet可能反而会更高效,耗时比entrySet少10%。总体来说还是推荐使用entrySet。因为当key很简单时,其性能或许会略低于keySet,但却是可控的;而随着key的复杂化,entrySet的优势将会明显体现出来;
  • 只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间;
  • 只遍历value时,使用vlaues方法是最佳选择,entrySet会略好于keySet方法。

2)如果使用TreeMap:

  • 同时遍历key和value时,与HashMap不同,entrySet的性能远远高于keySet。这是由TreeMap的查询效率决定的,也就是说,TreeMap查找value的开销较大,明显高于entrySet一次性取出所有key和value的开销。因此遍历TreeMap时强烈推荐使用entrySet方法;
  • 只遍历key时,keySet方法更为合适,因为entrySet将无用的value也给取出来了,浪费了性能和空间;
  • 只遍历value时,使用vlaues方法是最佳选择,entrySet也明显优于keySet方法。

Map遍历方式方式及性能测试


三、Map集合包含List的遍历

Map集合包含list的遍历

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值