Array,List,Set及Map遍历内容的方法探究

简介: Array,List,Set及Map遍历内容的方法探究

日常开发中我们经常遇到需要遍历集合内容的情况。有些是按照业务需求取得数据,存在特定的格式和要求。有些是为了调试的时候临时输出数据,对格式没有特定的要求。按照场合的不同可以选择恰当的遍历方法。

Array

数组是最基础的数据结构,最常见的打印方法就是遍历数组取得对应下标的元素,简单直接。简称它为"传统遍历法"

for (int i = 0; i < array.length; i++) { 
    System.out.println("array[" + i + "]:" + array[i]);
}

但是这种方法需要判断下标,存在下标越界的风险。JAVA为我们提供了更安全的方法,即"foreach法"。JVM将自行检查数组的边界,避免越界的可能。

for (int element : array) {
    System.out.println(element);
}

上述两种方法在打印元素的时候可以加入其它内容,或更改格式,或针对元素下标或者元素内容做业务处理,比如下标为2的时候改变元素值,元素内容为某值得时候不输出等等。


但是有时候我们既不想改格式,也没有针对某下标或某元素修改的需要,只想打印内容而已。这时候我们可以选择JDK提供的默认打印方法,简称为"工具类打印法"。这种打印方法简单快捷,将会以[x, x, ...]的固定格式输出。

System.out.println(Arrays.toString(array));

除了这三种方法还有一种间接的方法,将数组转为List后遍历,简称为"List转换法"

for (E e : Arrays.asList(array)) {
    System.out.println(e);
}
// 或直接将List作为参数输出
System.out.println(Arrays.asList(array));

将数组构建成List的时候内部将遍历一次数组,foreach或toString的时候又将遍历一次List,效率较为低下。


总结下这四种方法,如何选择。


如果只是为了打印数组内容,没有其他特别的需求,建议采用"工具类打印法"。


如果打印数组内容的时候有特定格式的需求,建议采用"foreach法"。


如果还需要针对下标做些判断,建议采用"传统遍历法"。


如果存在数组转为List的现有处理,可以采用采用"List转换法"。但要注意基本类型的数组不可以采用asList转为List,其他类型的数组可以转换,但转换后的List不可执行add和remove操作。具体原因在此不作展开。

List

List是集合框架中使用最为频繁的数据结构。打印List又有哪些方法?

有类似数组的"传统遍历法"

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

但是当List采用LinkedList实现的话,需要意识到该遍历方法的效率较低。因为其每次查询都需要在链表里遍历,时间复杂度为O(n),加上外部循环,总时间复杂度为O(n²)。即便LinkedList#get()针对遍历进行了优化,会先判断index处于size前半段还是后半段,前半段则从头部开始遍历,后半段则从尾部开始遍历。随着n的无限大,get()的时间复杂度仍趋近于O(n)。


当然也有效率较高的"foreach法"。

for (E e: list) {
    System.out.println(e);
}

Arraylist和LinkedList的父类AbstractCollection复写了toString()迭代元素以固定的"[x, x, ...]"格式输出集合内容。所以我们可以直接将其作为参数输出,简称为"直接打印法"

System.out.println(list);

我们可能会遇到在遍历List的时候需要删除某下标对应元素或某元素的需求。如果采用上面的"传统遍历法"遍历的时候删除元素的时候会导致size突变,极易发生IndexOutOfBoundsException。采用"foreach法"遍历的时候删除元素也不安全,因为取下个值的时候可能发生ConcurrentModificationException。原因在于迭代器取值的时候会判断内部暂存的modCount和List自己的modCount比较,两者不一致则抛出该异常。


而迭代器自己提供的remove函数在调用List的removeNode移除元素之后会将暂存的modCount同步,避免了ConcurrentModificationException的抛出。简称它为"迭代法"。

Iterator<E> elementIterator = list.iterator();
while (elementIterator.hasNext()) {
    E e = elementIterator.next();
    if (...) {
        elementIterator.remove();
    }
}

前面讲述数组的时候谈及了将Array转为List当做List打印的"List转换法",那么打印List自然也有转为Array当做数组打印的思路。简称为"Array转换法"。toArray()执行的时候将迭代一遍List,遍历数组的时候又将遍历一次,比较浪费性能。

E[] array = list.toArray();
// 传统遍历数组
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}
// foreach法遍历数组
for (E e : array) {
    System.out.println(e);
}

JAVA8推出了Lamba表达式和方法引用,利用该方法可以更简便地打印内容。简称为"Lamba遍历法"。Lamba表达式也是语法糖,用来起来简单方便,但是需要了解其原理。

// ① Lamba方式
// Lamba方式
list.forEach(item->System.out.println(item));
// ② 方法引用方式
list.forEach(System.out::println);
// 或
list.stream().forEach(System.out::println);

总结以上六种方法,如何选择。


如果只是单纯地打印内容采用"直接打印法"即可。


如果需要下标信息或者做些特殊处理,建议采用"传统遍历法",但注意LinkedList的时候避免采用该方案。


如果不需要下标信息但不希望以固定格式输出,建议采用"foreach法"。


如果在遍历的时候需要移除元素,必须采用"迭代法"。


如果现有代码里已经存在List转换为数组的处理的话,可以采用数组的打印方法,即"Array转换法"。


如果JAVA的版本是1.8及以上,可以试试"Lamba遍历法"。

Set

Set和List的区别在于Set内的元素是无序的,不重复的。不像List内元素的顺序是按照写入的顺序排放的,它的元素内容即使重复也不会覆盖而是继续在后面追加。结构特点都是类似的,所以遍历方法也差不多。

最常见的是"foreach法"打印其内容。

for (E e : set) {
    System.out.println(e);
}

和List一样为了避免ConcurrentModificationException的发生,还有"迭代器法"

Iterator<E> elementIterator = set.iterator();
while (elementIterator.hasNext()) {
    E e = elementIterator.next();
    if (...) {
        elementIterator.remove();
    }
}

抽象实现类也是AbstractCollection,所以也能当做参数直接打印。"直接打印法"

System.out.println(set);

并且Set也提供了转换为数组的方法,即可采用"Array转换法"

for (E e : set.toArray()) {
    System.out.println(e);
}

Set的遍历方法就不作总结了,选择的基本策略等同于List。

Map

Map并非继承自Collection接口,而是键值对的形式。其数据内容和List以及Set存在差异。在遍历方法上有其特有的方法,也有些类似的方法。


类似的地方在于其抽象实现类AbstractMap也复写了toString(),将以固定的{xx=yy, xx=yy, ...}格式打印Map内容。简称为"直接打印法"。

System.out.println(map);

因为是键值对的数据形式,所以Map不会提供默认的迭代器。进而无法直接采用foreach的方法去打印Map。但是Map针对key,value及键值对entry这三种输出内容分别提供了对应的集合实例供外部打印。


通过keySet()可以得到针对key的集合实例。进而采用foreach法或迭代法,简称为"key迭代法"。

Set<K> keySet = map.keySet(); 
for (K k : keySet) {
    System.out.println(k);
}

采用迭代写法可以像List和Set一样避免ConcurrentModificationException。

Iterator<K> keyIterator = map.keySet().iterator();
while (keyIterator.hasNext()) {
    K k = keyIterator.next();
    if (...) {
        keyIterator.remove();
    }
}

当利用keySet遍历Map的时候需要注意不要在拿到Key之后通过Map#get(Key)再取得Value。


一:因为HashMap等实现类的hash算法散列性不够的时候hash碰撞较为严重,导致get操作不再是数组结构的直接取值而是链表的遍历。效率会下降。


二:如果Map为LinkedHashMap实现的话,并且采用的是按访问顺序组织节点的话,在get的时候会导致节点顺序发生改变,进而导致下次取节点的时候发生ConcurrentModificationException。


keySet()可以拿到key列表以外还可以通过values()拿到value列表。简称为"value迭代法"。

Collection<V> valueCollection = map.values();
for (V v : valueCollection) {
    System.out.println(v);
}

还有避免抛出ConcurrentModificationException的迭代器法。

Iterator<V> valueIterator = map.values().iterator();
while (valueIterator.hasNext()) {
    V v = valueIterator.next();
    if (...) {
        valueIterator.remove();
    }
}

单单拿到key或value还不够,我们还需要能同时拿到它们。Map内部接口Entry就是声明了如何拿到key和value属性的接口。而通过entrySet()我们可以拿到各实现类实现的entry列表。简称为"entry迭代法"

Set<Map.Entry<K, V>> entrySet = map.entrySet();
for (Map.Entry<K, V> entry : entrySet) {
    System.out.println(entry.getKey() + entry.getValue());
}

同样的在得到各节点的时候便于删除节点的迭代器法。

Iterator<Map.Entry<K, V>> entryIterator = map.entrySet().iterator(); 
while (entryIterator.hasNext()) { 
    Map.Entry<K, V> entry = entryIterator.next(); 
    if (...) {
        entryIterator.remove();
    }
}

总结下如何选择上述打印方法。

单纯地查看内容,当然是"直接打印法"

如果只需要key列表,采用"key迭代法"。

同样的只需要value列表的化,采用"value迭代法"

两者都需要的话,采用"entry迭代法"

大家可以根据打印的具体需要选择恰当的打印方法,同时考虑一些注意点,避免打印的时候发生意想不到的错误。


相关文章
|
15天前
|
JavaScript 前端开发 开发者
|
1月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
19 1
|
1月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
2月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
37 6
【数据结构】map&set详解
|
1月前
|
存储 分布式计算 NoSQL
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
27 3
|
1月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
35 1
|
2月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
36 5
|
2月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
2月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
5月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
919 1