1. Java集合框架基础概念
在Java编程中,数组和集合是我们最常使用的数据结构。虽然它们都能存储多个元素,但在实现方式和操作特性上有着本质区别。数组是Java语言内置的基础数据结构,而集合则是通过Java集合框架(Java Collections Framework)提供的一系列接口和类实现的。
数组最大的特点是长度固定、类型统一,在内存中连续存储。而集合框架则提供了更灵活的数据结构,包括:
- List:有序可重复集合
- Set:无序不可重复集合
- Map:键值对映射结构
理解如何正确获取这些数据结构的长度以及高效遍历它们,是Java开发中的基本功。下面我将结合多年开发经验,详细解析各种情况下的最佳实践。
2. 数组的长度获取与遍历
2.1 数组长度获取
Java数组的长度是通过length属性获取的,这是一个final字段:
java复制int[] numbers = {1, 2, 3, 4, 5};
int length = numbers.length; // 返回5
注意:数组的length是属性不是方法,不要加括号。这是新手常犯的错误。
对于多维数组,length返回的是第一维的长度:
java复制int[][] matrix = new int[3][4];
System.out.println(matrix.length); // 输出3,不是12
2.2 数组遍历方式
2.2.1 传统for循环
java复制for(int i=0; i<numbers.length; i++) {
System.out.println(numbers[i]);
}
这是最基础的遍历方式,适合需要索引的场景。我在性能敏感代码中通常会优先使用这种方式。
2.2.2 增强for循环
java复制for(int num : numbers) {
System.out.println(num);
}
语法更简洁,但无法获取当前索引。在不需要索引时推荐使用,代码可读性更好。
2.2.3 Java 8 Stream API
java复制Arrays.stream(numbers).forEach(System.out::println);
函数式风格,适合链式操作。但在简单遍历时性能略低于前两种方式。
3. List集合的长度获取与遍历
3.1 List长度获取
List使用size()方法获取元素数量:
java复制List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
int size = list.size(); // 返回2
重要区别:数组用length属性,集合用size()方法。这个差异经常导致编译错误。
3.2 List遍历方式
3.2.1 普通for循环
java复制for(int i=0; i<list.size(); i++) {
String item = list.get(i);
System.out.println(item);
}
适合ArrayList等随机访问列表,但对于LinkedList性能较差(get(i)是O(n)操作)。
3.2.2 迭代器遍历
java复制Iterator<String> it = list.iterator();
while(it.hasNext()) {
String item = it.next();
System.out.println(item);
}
这是最通用的集合遍历方式,所有List实现都适用。也是线程安全遍历的一种方式。
3.2.3 增强for循环
java复制for(String item : list) {
System.out.println(item);
}
语法糖,底层也是使用迭代器。代码最简洁,我日常开发中最常用的方式。
3.2.4 ListIterator双向遍历
java复制ListIterator<String> lit = list.listIterator();
while(lit.hasNext()) {
String item = lit.next();
System.out.println(item);
}
// 反向遍历
while(lit.hasPrevious()) {
String item = lit.previous();
System.out.println(item);
}
特有功能:可以向前遍历、修改元素、获取索引等。
3.2.5 Java 8 forEach
java复制list.forEach(System.out::println);
函数式风格,简洁但调试不太方便。
4. Set集合的长度获取与遍历
4.1 Set长度获取
同样使用size()方法:
java复制Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
int size = set.size(); // 返回2
4.2 Set遍历方式
Set没有索引概念,因此不能使用普通for循环遍历。
4.2.1 迭代器遍历
java复制Iterator<String> it = set.iterator();
while(it.hasNext()) {
String item = it.next();
System.out.println(item);
}
4.2.2 增强for循环
java复制for(String item : set) {
System.out.println(item);
}
4.2.3 Java 8 forEach
java复制set.forEach(System.out::println);
注意:HashSet的遍历顺序是不确定的,如果需要有序遍历,请使用LinkedHashSet或TreeSet。
5. Map集合的长度获取与遍历
5.1 Map长度获取
同样使用size()方法:
java复制Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
int size = map.size(); // 返回2
5.2 Map遍历方式
5.2.1 遍历键集合
java复制for(String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
5.2.2 遍历值集合
java复制for(Integer value : map.values()) {
System.out.println(value);
}
5.2.3 遍历键值对集合
java复制for(Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
这是最高效的遍历方式,推荐使用。
5.2.4 使用迭代器
java复制Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
5.2.5 Java 8 forEach
java复制map.forEach((k, v) -> System.out.println(k + "=" + v));
6. 性能比较与最佳实践
6.1 长度获取性能
- 数组length属性:O(1),直接读取字段
- 集合size()方法:通常O(1),但某些并发集合可能是O(n)
6.2 遍历性能比较
| 遍历方式 | 数组 | ArrayList | LinkedList | HashSet | TreeSet | HashMap | TreeMap |
|---|---|---|---|---|---|---|---|
| 普通for循环 | 快 | 快 | 慢 | 不支持 | 不支持 | 不支持 | 不支持 |
| 增强for循环 | 快 | 快 | 中 | 中 | 中 | 中 | 中 |
| 迭代器 | 中 | 中 | 中 | 中 | 中 | 中 | 中 |
| Java 8 forEach | 中 | 中 | 中 | 中 | 中 | 中 | 中 |
6.3 线程安全考虑
在并发环境下,遍历集合时需要注意:
- 使用Collections.synchronizedXXX包装集合
- 遍历时手动同步
- 使用并发集合类如ConcurrentHashMap
- 使用迭代器的fail-fast特性检测并发修改
java复制Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// 安全遍历方式
synchronized(syncMap) {
for(Map.Entry<String, Integer> entry : syncMap.entrySet()) {
// 操作entry
}
}
7. 常见问题与解决方案
7.1 ConcurrentModificationException
这是遍历集合时修改集合导致的常见异常。解决方案:
- 使用迭代器的remove()方法而不是集合的remove()
- 遍历前复制一份新集合
- 使用并发集合类
7.2 空集合判断
不要用size()==0判断集合是否为空,应该:
java复制if(list.isEmpty()) { // 不要用list.size() == 0
// 空集合处理
}
isEmpty()通常性能更好,语义更明确。
7.3 遍历时性能优化
- 对于ArrayList,优先使用普通for循环
- 避免在循环内重复调用size()方法
- 考虑使用并行流处理大数据集
java复制// 不好的写法
for(int i=0; i<list.size(); i++) {...}
// 好的写法
int size = list.size();
for(int i=0; i<size; i++) {...}
8. Java 8/11新特性
8.1 Lambda表达式遍历
java复制list.forEach(item -> System.out.println(item));
map.forEach((k, v) -> System.out.println(k + ":" + v));
8.2 Stream API遍历
java复制list.stream()
.filter(item -> item.startsWith("A"))
.forEach(System.out::println);
8.3 并行遍历
java复制list.parallelStream().forEach(System.out::println);
适合大数据集,但要注意线程安全问题。
在实际项目中,我通常会根据具体场景选择合适的遍历方式。对于性能关键代码,建议进行基准测试。记住,可读性和可维护性往往比微小的性能差异更重要。