#畅聊专区#在Java编程中,HashMap 是一种非常常见的数据结构。我们经常需要对其中的键值对进行遍历。通常有多种方法可以遍历 HashMap,其中一种方法是使用 keySet() 方法。


然而,很多Java大佬并不推荐这种方法。为什么呢?


keySet() 方法的工作原理


首先,让我们来看一下 keySet() 方法是如何工作的。keySet() 方法返回 HashMap 中所有键的集合 (Set<K>)。然后我们可以使用这些键来获取相应的值。


代码示例如下:

// 创建一个HashMap并填充数据
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);

// 使用keySet()方法遍历HashMap
for (String key : map.keySet()) {
    // 通过键获取相应的值
    Integer value = map.get(key);
    System.out.println("Key: " + key + ", Value: " + value);
}


这个代码看起来没什么问题,但在性能和效率上存在一些隐患。


keySet() 方法的缺点

1、 多次哈希查找:如上面的代码所示,使用 keySet() 方法遍历时,需要通过键去调用 map.get(key) 方法来获取值。这意味着每次获取值时,都需要进行一次哈希查找操作。如果 HashMap 很大,这种方法的效率就会明显降低。

2、 额外的内存消耗keySet() 方法会生成一个包含所有键的集合。虽然这个集合是基于 HashMap 的键的视图,但仍然需要额外的内存开销来维护这个集合的结构。如果 HashMap 很大,这个内存开销也会变得显著。

3、 代码可读性和维护性:使用 keySet() 方法的代码可能会让人误解,因为它没有直接表现出键值对的关系。在大型项目中,代码的可读性和维护性尤为重要。

<顺便吆喝一声,技术大厂内tui,前后端测试捞人,感兴趣可试试>


更好的选择:entrySet() 方法


相比之下,使用 entrySet() 方法遍历 HashMap 是一种更好的选择。entrySet() 方法返回的是 HashMap 中所有键值对的集合 (Set<Map.Entry<K, V>>)。通过遍历这个集合,我们可以直接获取每个键值对,从而避免了多次哈希查找和额外的内存消耗。

下面是使用 entrySet() 方法的示例代码:

// 创建一个HashMap并填充数据
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);

// 使用entrySet()方法遍历HashMap
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    // 直接获取键和值
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println("Key: " + key + ", Value: " + value);
}


entrySet() 方法的优势

1、 避免多次哈希查找:在遍历过程中,我们可以直接从 Map.Entry 对象中获取键和值,而不需要再次进行哈希查找,提高了效率。

2、 减少内存消耗entrySet() 方法返回的是 HashMap 内部的一个视图,不需要额外的内存来存储键的集合。

3、 提高代码可读性entrySet() 方法更直观地表现了键值对的关系,使代码更加易读和易维护。


性能比较

我们来更深入地解析性能比较,特别是 keySet()entrySet() 方法在遍历 HashMap 时的性能差异。


主要性能问题

1、 多次哈希查找: 使用 keySet() 方法遍历 HashMap 时,需要通过键调用 map.get(key) 方法获取值。这意味着每次获取值时都需要进行一次哈希查找操作。哈希查找虽然时间复杂度为 O(1),但在大量数据下,频繁的哈希查找会累积较高的时间开销。

2、 额外的内存消耗keySet() 方法返回的是一个包含所有键的集合。虽然这个集合是基于 HashMap 的键的视图,但仍然需要额外的内存来维护这个集合的结构。


更高效的选择:entrySet() 方法

相比之下,entrySet() 方法返回的是 HashMap 中所有键值对的集合 (Set<Map.Entry<K, V>>)。通过遍历这个集合,我们可以直接获取每个键值对,避免了多次哈希查找和额外的内存消耗。


性能比较示例


让我们通过一个具体的性能比较示例来详细说明:

import java.util.HashMap;
import java.util.Map;

public class HashMapTraversalComparison {
    public static void main(String[] args) {
        // 创建一个大的HashMap
        Map<String, Integer> map = new HashMap<>();
        for (int i = 0; i < 1000000; i++) {
            map.put("key" + i, i);
        }

        // 测试keySet()方法的性能
        long startTime = System.nanoTime(); // 记录开始时间
        for (String key : map.keySet()) {
            Integer value = map.get(key); // 通过键获取值
        }
        long endTime = System.nanoTime(); // 记录结束时间
        System.out.println("keySet() 方法遍历时间: " + (endTime - startTime) + " 纳秒");

        // 测试entrySet()方法的性能
        startTime = System.nanoTime(); // 记录开始时间
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey(); // 直接获取键
            Integer value = entry.getValue(); // 直接获取值
        }
        endTime = System.nanoTime(); // 记录结束时间
        System.out.println("entrySet() 方法遍历时间: " + (endTime - startTime) + " 纳秒");
    }
}


深度解析性能比较示例


1、 创建一个大的 HashMap

Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < 1000000; i++) {
    map.put("key" + i, i);
}
  • 创建一个包含100万个键值对的 HashMap
  • "key" + ii
  • 这个 HashMap 足够大,可以明显展示两种遍历方法的性能差异。


2、 测试 keySet() 方法的性能

long startTime = System.nanoTime(); // 记录开始时间
for (String key : map.keySet()) {
    Integer value = map.get(key); // 通过键获取值
}
long endTime = System.nanoTime(); // 记录结束时间
System.out.println("keySet() 方法遍历时间: " + (endTime - startTime) + " 纳秒");
  • 使用 keySet() 方法获取所有键,并遍历这些键。
  • 在每次迭代中,通过 map.get(key) 方法获取值。
  • 记录开始时间和结束时间,计算遍历所需的总时间。


3、 测试 entrySet() 方法的性能

startTime = System.nanoTime(); // 记录开始时间
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey(); // 直接获取键
    Integer value = entry.getValue(); // 直接获取值
}
endTime = System.nanoTime(); // 记录结束时间
System.out.println("entrySet() 方法遍历时间: " + (endTime - startTime) + " 纳秒");
  • 使用 entrySet() 方法获取所有键值对,并遍历这些键值对。
  • 在每次迭代中,直接从 Map.Entry 对象中获取键和值。
  • 记录开始时间和结束时间,计算遍历所需的总时间。


性能结果分析

假设上述代码的运行结果如下:

keySet() 方法遍历时间: 1200000000 纳秒
entrySet() 方法遍历时间: 800000000 纳秒


可以看出,使用 entrySet() 方法的遍历时间明显短于 keySet() 方法。这主要是因为:


1、 避免了多次哈希查找: 使用 keySet() 方法时,每次获取值都需要进行一次哈希查找。而使用 entrySet() 方法时,键和值直接从 Map.Entry 对象中获取,无需再次查找。


2、 减少了内存消耗: 使用 keySet() 方法时,额外生成了一个包含所有键的集合。而使用 entrySet() 方法时,返回的是 HashMap 内部的一个视图,无需额外的内存开销。


小结一下

通过性能比较示例,我们可以清楚地看到 entrySet() 方法在遍历 HashMap 时的效率优势。使用 entrySet() 方法不仅能避免多次哈希查找,提高遍历效率,还能减少内存消耗。


综上所述,在遍历 HashMap 时,entrySet() 方法是更优的选择。


几种高效的替代方案

除了 entrySet() 方法外,还有其他几种高效的替代方案,可以用于遍历 HashMap

以下是几种常见的高效替代方案及其优缺点分析:


1. 使用 entrySet() 方法

我们已经讨论过,entrySet() 方法是遍历 HashMap 时的一个高效选择。它直接返回键值对的集合,避免了多次哈希查找,减少了内存开销。

import java.util.HashMap;
import java.util.Map;

public class EntrySetTraversal {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("cherry", 3);

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}


2. 使用 forEach 方法

从 Java 8 开始,Map 接口提供了 forEach 方法,可以直接对每个键值对进行操作。这种方式利用了 lambda 表达式,代码更简洁,可读性强。

import java.util.HashMap;
import java.util.Map;

public class ForEachTraversal {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("cherry", 3);

        map.forEach((key, value) -> {
            System.out.println("Key: " + key + ", Value: " + value);
        });
    }
}


3. 使用 iterator 方法

另一种遍历 HashMap 的方法是使用迭代器 (Iterator)。这种方法适用于需要在遍历过程中对集合进行修改的情况,比如删除某些元素。

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class IteratorTraversal {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("cherry", 3);

        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}


4. 使用 Streams API

Java 8 引入了 Streams API,可以结合 stream() 方法和 forEach 方法来遍历 HashMap。这种方法可以对集合进行更复杂的操作,比如过滤、映射等。

import java.util.HashMap;
import java.util.Map;

public class StreamTraversal {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("cherry", 3);

        map.entrySet().stream().forEach(entry -> {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        });
    }
}


优缺点分析

entrySet() 方法

  • 优点:避免多次哈希查找,减少内存消耗,代码简单明了。
  • 缺点:没有特定缺点,在大多数情况下是最佳选择。

forEach 方法

  • 优点:代码简洁,可读性强,充分利用 lambda 表达式。
  • 缺点:仅适用于 Java 8 及以上版本。

iterator 方法

  • 优点:适用于需要在遍历过程中修改集合的情况,如删除元素。
  • 缺点:代码稍显繁琐,不如 entrySet()forEach 方法直观。

Streams API 方法

  • 优点:支持复杂操作,如过滤、映射等,代码简洁。
  • 缺点:仅适用于 Java 8 及以上版本,性能在某些情况下可能不如 entrySet()forEach


结论

在遍历 HashMap 时,entrySet() 方法是一个高效且广泛推荐的选择。对于更现代的代码风格,forEach 方法和 Streams API 提供了简洁且强大的遍历方式。如果需要在遍历过程中修改集合,可以使用 iterator 方法。根据具体需求选择合适的遍历方法,可以显著提高代码的效率和可读性。


作者:架构师专栏

出处:juejin.cn/post/7393663398406799372

#畅聊专区#

还没有评论,抢个沙发!