Java 遍历HashMap并remove、put

本文详细解析了在Java中使用HashMap时,如何避免在遍历过程中进行修改导致的并发修改异常,并介绍了正确的删除元素方法。
  1. package net.nie.test;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Iterator;  
  5. import java.util.Map;  
  6.   
  7. public class HashMapTest {  
  8.    private static Map<Integer, String> map=new HashMap<Integer,String>();  
  9.       
  10.    /**  1.HashMap 类映射不保证顺序;某些映射可明确保证其顺序: TreeMap 类 
  11.     *   2.在遍历Map过程中,不能用map.put(key,newVal),map.remove(key)来修改和删除元素, 
  12.     *   会引发 并发修改异常,可以通过迭代器的remove(): 
  13.     *   从迭代器指向的 collection 中移除当前迭代元素 
  14.     *   来达到删除访问中的元素的目的。   
  15.     *   */   
  16.    public static void main(String[] args) {  
  17.         map.put(1,"one");  
  18.         map.put(2,"two");  
  19.         map.put(3,"three");  
  20.         map.put(4,"four");  
  21.         map.put(5,"five");  
  22.         map.put(6,"six");  
  23.         map.put(7,"seven");  
  24.         map.put(8,"eight");  
  25.         map.put(5,"five");  
  26.         map.put(9,"nine");  
  27.         map.put(10,"ten");  
  28.         Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();  
  29.         while(it.hasNext()){  
  30.             Map.Entry<Integer, String> entry=it.next();  
  31.             int key=entry.getKey();  
  32.             if(key%2==1){  
  33.                 System.out.println("delete this: "+key+" = "+key);  
  34.                 //map.put(key, "奇数");   //ConcurrentModificationException  
  35.                 //map.remove(key);      //ConcurrentModificationException  
  36.                 it.remove();        //OK   
  37.             }  
  38.         }  
  39.         //遍历当前的map;这种新的for循环无法修改map内容,因为不通过迭代器。  
  40.         System.out.println("-------\n\t最终的map的元素遍历:");  
  41.         for(Map.Entry<Integer, String> entry:map.entrySet()){  
  42.             int k=entry.getKey();  
  43.             String v=entry.getValue();  
  44.             System.out.println(k+" = "+v);  
  45.         }  
  46.     }  
  47. }  

只能通过iterator的remove实现删除和增加。

分析原因

其实上面的三种遍历方式从根本上讲都是使用的迭代器,之所以出现不同的结果是由于remove操作的实现不同决定的。

首先前两种方法都在调用nextEntry方法的同一个地方抛出了异常

 

1

2

3

4

5

6

7

    final Entry<K,V> nextEntry() {

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

            Entry<K,V> e = next;

            ...

            ...

     }

这里modCount是表示map中的元素被修改了几次(在移除,新加元素时此值都会自增),而expectedModCount是表示期望的修改次数,在迭代器构造的时候这两个值是相等,如果在遍历过程中这两个值出现了不同步就会抛出ConcurrentModificationException异常。

1、HashMap的remove方法实现

 

1

2

3

4

public V remove(Object key) {

    Entry<K,V> e = removeEntryForKey(key);

    return (e == null ? null : e.value);

}

2、HashMap.KeySet的remove方法实现

 

 
  1. public boolean remove(Object o) {

  2. return HashMap.this.removeEntryForKey(o) != null;

  3. }


 

 

 

3、HashMap.HashIterator的remove方法实现

 

1

2

3

4

5

6

7

8

9

10

public void remove() {

   if (current == null)

        throw new IllegalStateException();

   if (modCount != expectedModCount)

        throw new ConcurrentModificationException();

   Object k = current.key;

   current = null;

   HashMap.this.removeEntryForKey(k);

   expectedModCount = modCount;

}

以上三种实现方式都通过调用HashMap.removeEntryForKey方法来实现删除key的操作。在removeEntryForKey方法内只要移除了key modCount就会执行一次自增操作,此时modCount就与expectedModCount不一致了,上面三种remove实现中,只有第三种iterator的remove方法在调用完removeEntryForKey方法后同步了expectedModCount值与modCount相同,所以在遍历下个元素调用nextEntry方法时,iterator方式不会抛异常。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

    final Entry<K,V> removeEntryForKey(Object key) {

        int hash = (key == null) ? 0 : hash(key.hashCode());

        int i = indexFor(hash, table.length);

        Entry<K,V> prev = table[i];

        Entry<K,V> e = prev;

 

        while (e != null) {

            Entry<K,V> next = e.next;

            Object k;

            if (e.hash == hash &&

                ((k = e.key) == key || (key != null && key.equals(k)))) {

                modCount++;

                size--;

                if (prev == e)

                    table[i] = next;

                else

                    prev.next = next;

                e.recordRemoval(this);

                return e;

            }

            prev = e;

            e = next;

        }

 

        return e;

    }

 

发散

1、如果是遍历过程中增加或修改数据呢?
增加或修改数据只能通过Map的put方法实现,在遍历过程中修改数据可以,但如果增加新key就会在下次循环时抛异常,因为在添加新key时modCount也会自增。

2、有些集合类也有同样的遍历问题,如ArrayList,通过Iterator方式可正确遍历完成remove操作,直接调用list的remove方法就会抛异常。

 

 
  1. //会抛ConcurrentModificationException异常

  2. for(String str : list){

  3. list.remove(str);

  4. }

  5.  
  6. //正确遍历移除方式

  7. Iterator<String> it = list.iterator();

  8. while(it.hasNext()){

  9. it.next();

  10. it.remove();

  11. }


 

 

 

 

3、jdk为什么这样设计,只允许通过iterator进行remove操作?
HashMap和keySet的remove方法都可以通过传递key参数删除任意的元素,而iterator只能删除当前元素(current),一旦删除的元素是iterator对象中next所正在引用的,如果没有通过modCount、 expectedModCount的比较实现快速失败抛出异常,下次循环该元素将成为current指向,此时iterator就遍历了一个已移除的过期数据

 

转载自https://blog.youkuaiyun.com/e01014165/article/details/52174520

https://blog.youkuaiyun.com/weikzhao0521/article/details/53022803?locationNum=10&fps=1

<think>首先,用户的问题是:“我想查找Java遍历HashMap的方法请问java遍历hashmap方法”。这明确表示用户想知道在Java中如何遍历HashMap。从系统级指令中,我需要:-使用$...$格式表示行内数学表达式(如果有),但这里可能不涉及数学,所以可能不需要。-独立公式用$$...$$格式,但同样,这里可能不适用。-LaTeX语法正确。-使用中文回答。-生成相关问题。-在回答中引用的段落末尾自然地添加引用标识,如[^1]。-回答结构清晰,逐步解决问题,保证真实可靠。用户提供的引用是关于JavaHashMap遍历方法的:-引用[1]:讨论五种遍历方法。-引用[2]:介绍三种遍历方式,特别是使用values()方法。-引用[3]:展示遍历key、value和key/value对的方法。我需要基于这些引用构建回答。用户上次的问题和我的回答没有被提供,所以我只能基于当前输入。回答结构:1.**引言**:简要介绍HashMap遍历的重要性。2.**详细方法**:逐步解释各种遍历方法,使用代码示例。3.**优缺点**:讨论每种方法的适用场景和优缺点。4.**总结**:推荐最佳实践。5.**相关问题**:生成相关问题。确保使用中文,添加引用标识。例如,当引用特定部分时,添加[^1]等。关键遍历方法从引用中提取:-使用entrySet()遍历key/value对。-使用keySet()遍历keys。-使用values()遍历values。-使用forEach循环(Java8及以上)。从引用[3]:-forEach循环遍历entrySet。-遍历keySet。-遍历values。从引用[1]:五种方法,可能包括迭代器等。引用[1]提到五种方法:1.使用keySet()和get()方法(不高效)。2.使用entrySet()和迭代器。3.使用entrySet()和forEach循环。4.使用keySet()和forEach循环。5.使用values()方法。但在引用[3]中,有明确的代码示例。我将基于引用[3]构建,因为它有代码示例。回答内容大纲:-**开头**:解释HashMap遍历的必要性。-**方法1:遍历key/value对**(使用entrySet())。-**方法2:遍历keys**(使用keySet())。-**方法3:遍历values**(使用values())。-**方法4:使用Java8forEach**(lambda表达式)。-**优缺点总结**。-**相关问题**。添加引用标识:在描述方法时,引用相关段落。例如:-当描述entrySet时,引用[3]。-当描述values时,引用[2]。生成相关问题:-类似示例中的快速排序问题。-例如:1.遍历HashMap的性能比较?2.如何在遍历时修改HashMap?3.HashMap与其他Map实现的遍历区别?确保回答真实可靠:基于标准JavaAPI。最终回答结构:1.引言。2.方法解释和代码。3.总结。4.相关问题。</think>### Java遍历HashMap的方法 在Java编程中,HashMap是一种常用的数据结构,用于存储键值对(key-value pairs)。遍历HashMap是常见的操作,例如在数据处理、集合迭代或数据展示中。遍历方法的选择取决于具体需求:是否需要访问键、值,或两者兼有。以下是基于Java标准API的几种主流遍历方法,每种方法都有其适用场景和优缺点。我将使用中文逐步解释,提供代码示例(基于Java 8及以上版本)。所有代码示例均使用标准语法,确保真实可靠。 #### 1. **遍历键值对(key/value对)** 当需要同时访问HashMap中的键和值时,推荐使用`entrySet()`方法。它返回一个`Set<Map.Entry<K, V>>`集合,每个元素包含一个键值对。这种方法高效且直接。 - **代码示例**: ```java import java.util.HashMap; import java.util.Map; public class HashMapTraversal { public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); map.put("Alice", 30); map.put("Bob", 25); map.put("Charlie", 35); // 使用entrySet()遍历键值对 System.out.println("遍历键值对:"); for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } } } ``` - **优点**:高效,避免额外的`get()`调用,时间复杂度接近$O(n)$(n为元素数)。 - **缺点**:代码稍显冗长,不适合仅需键或值的场景。 - **适用场景**:需要同时处理键和值,如数据转换或日志输出[^3]。 #### 2. **遍历键(keys)** 如果只关心HashMap的键,可以使用`keySet()`方法。它返回一个`Set<K>`集合,包含所有键。 - **代码示例**: ```java // 在同一个main方法中 System.out.println("\n遍历键:"); for (String key : map.keySet()) { System.out.println("Key: " + key); } ``` - **优点**:简单直观,适合基于键的操作,如检查存在性或过滤。 - **缺点**:无法直接访问值,如果需要值,需额外调用`map.get(key)`,这会增加时间复杂度到$O(n^2)$(在HashMap中get操作平均$O(1)$,但遍历时可能退化)。 - **适用场景**:键的遍历或查找,如权限验证[^3]。 #### 3. **遍历值(values)** 当仅需处理HashMap的值时,`values()`方法是最佳选择。它返回一个`Collection<V>`集合。 - **代码示例**: ```java // 在同一个main方法中 System.out.println("\n遍历值:"); for (Integer value : map.values()) { System.out.println("Value: " + value); } ``` - **优点**:代码简洁,性能高效(时间复杂度$O(n)$),不涉及键处理。 - **缺点**:无法获取键信息,限制了应用范围。 - **适用场景**:统计或聚合值,如计算总和或平均值[^2]。 #### 4. **使用Java 8的forEach和Lambda表达式** 在Java 8及以上,可以利用`forEach`方法和Lambda表达式简化遍历,尤其适合函数式编程风格。 - **代码示例(遍历键值对)**: ```java // 在同一个main方法中 System.out.println("\n使用forEach遍历:"); map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value)); ``` - **优点**:代码极简,可读性强,支持行处理(如`parallelStream()`)。 - **缺点**:Lambda语法对新手可能不友好,且不适用于旧版Java。 - **适用场景**:现代Java项目中的快速迭代或流式处理[^1]。 #### 总结与最佳实践 - **推荐方法**:优先使用`entrySet()`或`forEach`遍历键值对,因为它们在性能和灵活性上最优。避免使用`keySet()`加`get()`的组合,以防性能下降。 - **性能注意**:HashMap遍历的平均时间复杂度为$O(n)$,但受哈希冲突影响可能退化。在遍历时修改HashMap(如删除元素)需使用`Iterator`的`remove()`方法以避免`ConcurrentModificationException`。 - **总体建议**:根据需求选择方法—需要键值对时用`entrySet`,仅需值时用`values`,现代代码优先用`forEach`[^1][^2][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值