2013-04-23 64 views
13

不确定在迭代下面代码中LinkedHashMap结构时触发java.util.ConcurrentModificationException的是什么。使用Map.Entry的方法很好。没有得到一个很好的解释,从以前的帖子引发这是什么。ConcurrentModificationException和LinkedHashMap

任何帮助,将不胜感激。

import java.util.LinkedHashMap; 
import java.util.Map; 

public class LRU { 

    // private Map<String,Integer> m = new HashMap<String,Integer>(); 
    // private SortedMap<String,Integer> lru_cache = Collections.synchronizedSortedMap(new TreeMap<String, Integer>()); 

    private static final int MAX_SIZE = 3; 

    private LinkedHashMap<String,Integer> lru_cache = new LinkedHashMap<String,Integer>(MAX_SIZE, 0.1F, true){ 
     @Override 
     protected boolean removeEldestEntry(Map.Entry eldest) { 
      return(lru_cache.size() > MAX_SIZE); 
     } 
    };  

    public Integer get1(String s){ 
     return lru_cache.get(s);   
    } 

    public void displayMap(){ 
     /** 
     * Exception in thread "main" java.util.ConcurrentModificationException 
      at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:373) 
      at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:384) 
      at LRU.displayMap(LRU.java:23) 
      at LRU.main(LRU.java:47) 
     */ 
     *for(String key : lru_cache.keySet()){ 
      System.out.println(lru_cache.get(key)); 
     }* 

// This parser works fine   
//  for(Map.Entry<String, Integer> kv : lru_cache.entrySet()){ 
//   System.out.println(kv.getKey() + ":" + kv.getValue()); 
//  } 
    } 

    public void set(String s, Integer val){ 
     if(lru_cache.containsKey(s)){    
      lru_cache.put(s, get1(s) + val); 
     } 
     else{ 
      lru_cache.put(s, val); 
     } 
    } 

    public static void main(String[] args) { 

     LRU lru = new LRU(); 
     lru.set("Di", 1); 
     lru.set("Da", 1); 
     lru.set("Daa", 1); 
     lru.set("Di", 1);   
     lru.set("Di", 1); 
     lru.set("Daa", 2); 
     lru.set("Doo", 2); 
     lru.set("Doo", 1);   
     lru.set("Sa", 2); 
     lru.set("Na", 1); 
     lru.set("Di", 1); 
     lru.set("Daa", 1); 

     lru.displayMap(); 

    } 

} 
+2

当您将初始容量设置为MAX_SIZE时,是否真的意味着加载因子为0.1?负载因子所做的是确保至少有size()/ load_factor的容量。即当你有3个键时,你的容量至少为30(实际上是32,因为它必须是2的幂)。我建议使用'MAX_SIZE * 10/7'和'0.7f'的默认加载因子,在这种情况下会给你一个4的容量。 – 2013-04-24 07:23:08

回答

8

您使用的访问顺序链接的哈希映射:从http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html规范,

结构上的修改是指添加或删除一个或多个 映射或在任何操作访问有序链接哈希映射的情况下, 影响迭代顺序。在插入顺序链接散列映射中,仅更改与已包含在 中的键相关的值,映射不是结构修改。在访问顺序链接 哈希映射中,仅查询与获取地图是结构性 修改。)

简单地调用get足以被认为是结构上的修改,引发异常。如果使用entrySet()序列,则只查询条目而不查询地图,因此不会触发ConcurrentModificationException

31

阅读Javadoc for LinkedHashMap

结构上的修改是,添加或删除一个或 多个映射,或者在访问顺序链接的哈希映射的情况下的任何操作, 影响迭代顺序。在插入顺序链接散列映射中,仅更改与已包含在 中的键相关的值,映射不是结构修改。 在访问顺序链接的 散列映射中,仅使用get查询映射是一个结构性的 修改。

既然你在true传递给LinkedHashMap构造函数,它是在访问顺序,当你试图从一个get的东西,你在结构上进行修改。

另请注意,当您使用增强型for语法时,实际上使用的是迭代器。从JLS §14.14.2简化报价:

增强for声明的形式为:

EnhancedForStatement: 

for (TargetType Identifier : Expression) Statement 

[...]

如果表达的类型是Iterable<X>某种类型的子类型 自变量X,然后让Ijava.util.Iterator<X>类型;否则, 让I是原始类型java.util.Iterator

增强for语句相当于 基本for语句的形式:

for (I #i = Expression.iterator(); #i.hasNext();) { 
    TargetType Identifier = 
     (TargetType) #i.next(); 
    Statement 
} 

#i是自动生成的标识符是从处于 任何其他标识符(自动生成的或以其他方式)不同范围(第6.3节)在增强型for语句发生的位置。

此外,在Javadoc中LinkedHashMap

由所有此类的集合视图方法返回的集合 的iterator方法返回的迭代器是 快速失败的:如果在迭代器创建后的任何时候,除了通过迭代器自己的方法,迭代器都会抛出 ConcurrentModificationException

因此,当你调用地图上get,你给它进行结构修饰,导致在增强,对抛出异常的迭代器。我觉得你的意思要做到这一点,从而避免了调用get

for (Integer i : lru_cache.values()) { 
    System.out.println(i); 
} 
3

LinkedHashMap传递true得到LRU行为(指驱逐策略构造函数中访问顺序而不是false插入顺序)。

所以每次调用get(key)时间底层Map.Entry的递增访问计数器并通过(最近访问)的Map.Entry移动到列表的头部重新排列集合。

迭代器(由for循环隐式创建)检查修改后的标志,它与原来的拷贝不同,因此抛出ConcurrentModificationException

为了避免这种情况,你应该使用entrySet()作为实现从继承的java.util.HashMap,因此迭代器不检查修改标志:

for(Map.Entry<String,Integer> e : lru_cache.entrySet()){ 
     System.out.println(e.getValue()); 
    } 

意识到这个类不是线程安全的所以在并发环境中,您需要使用像Collections.synchronizedMap(Map)这样可能很昂贵的警卫。在这种情况下,更好的选项可能是Google's Guava Cache

1

您的代码

for(String key : lru_cache.keySet()){ 
    System.out.println(lru_cache.get(key)); 
} 

实际上编译到:

Iterator<String> it = lru_cache.keySet().iterator(); 
while (it.hasNext()) { 
    String key = it.next(); 
    System.out.println(lru_cache.get(key)); 
} 

接下来,您LRU缓存缩减自己MAX_SIZE元素没有要求set()的时候,但调用get()时 - 这些答案,解释原因。

因此,我们有以下行为:

    创建
  • 新的迭代器遍历lru_cache.keySet()收集
  • lru_cache.get()调用从缓存中提取元素
  • get()调用截断lru_cache到MAX_SIZE元素(在你的案件3 )
  • 迭代器it由于集合修改而变为无效,并在下一次迭代时抛出。
1

这是集合框架的故障快速行为,当你修改列表(通过添加或删除元素),而遍历列表时会出现这个错误,并且Iterator会在那里。后来我偶然发现了这个错误。请参阅下面的主题以了解详细信息。

ConcurrentModificationException when adding inside a foreach loop in ArrayList

虽然此说数组列表,它适用于大部分的集合(或多个)数据strucutres的。

Concurrent Modification Exception : adding to an ArrayList

http://docs.oracle.com/javase/6/docs/api/java/util/ConcurrentModificationException.html

2

java.util.ConcurrentModificationException:如果有任何结构上的改变(添加,删除,重散列等),以基础列表而迭代存在。迭代器会在每次操作之前检查列表是否已更改。这被称为“故障安全操作”。

如果一个线程直接修改的集合,同时它遍历了快速失败的迭代器集合,迭代器将抛出此exception.Here同时使用迭代器,因为调用get()结构上修改了地图,你不能调用get()方法因此下一次调用其中一个迭代器方法失败并抛出ConcurrentModificationException