如何正确使用"合并" ConcurrentHashMap中的方法?

时间:2017-10-13 21:27:23

标签: java java.util.concurrent concurrenthashmap

我正在尝试解决练习5,第6章第6章; Java SE 8 for Really Impatient" by" Cay S Horstman"。这是一个问题:

编写一个应用程序,其中多个线程读取文件集合中的所有单词。使用ConcurrentHashMap>跟踪每个单词出现的文件。使用合并方法更新地图。

我试着像这样解决这个练习: 我创建了4个文件file1.txt,file2.txt,file3.txt,file4.txt

file1.txt包含以下内容:

Word1

file2.txt包含以下内容:

Word1 Word2

file3.txt包含以下内容:

Word1 Word2 Word3

file4.txt包含以下内容:

Word1 Word2 Word3 Word4

我实现了一个类" Problem5.java"获取指定目录中上述4个文件的列表。这个类有一个静态的ConcurrentHashMap" stringToFileMap"将使用" merge"更新方法。此类为每个文件创建一个StringToFileMapper对象,然后使用executor服务调用所有可调用对象。从执行程序服务返回期货后,它会打印出并发哈希映射的内容。

Problem5.java

package problem5;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;

public class Problem5 {
public static final ConcurrentHashMap<String, Set<File>>  stringToFileMap = new ConcurrentHashMap<>();
private File[] files;
private ExecutorService  executorService;
public Problem5(String dirName){
    executorService = Executors.newFixedThreadPool(2);
    File dir =  new File(dirName);
    files = dir.listFiles((dir1, name) -> name.endsWith(".txt"));
}

public void execute() throws InterruptedException, ExecutionException {
    List<Future<Long>> futureList;
    Collection<StringToFileMapper> callables = new ArrayList<>();
    for(File file: files){
        callables.add(new StringToFileMapper(file));
    }
    futureList = executorService.invokeAll(callables);
    stringToFileMap.forEach((String key, Set<File> files) -> {
        StringBuilder fileNames = new StringBuilder();
        for(File file: files){
            fileNames.append(file.getName());
            fileNames.append(", ");

        }
        System.out.println(key+" is present in files "+fileNames.toString());
    });
    System.out.println("Hashmap size = "+ stringToFileMap.size());
    executorService.shutdown();
    stringToFileMap.clear();
}
}

我实现了一个可调用的类&#34; StringToFileMapper.java&#34;从文件中读取单词并更新concurrentHashmap&#34; stringToFileMap&#34;。

StringToFileMapper.java:

package problem5;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.stream.Stream;

public final class StringToFileMapper implements Callable<Long> {

private final File file;
public StringToFileMapper(File file){
    this.file = file;
}


@Override
public Long call() throws Exception {
    Long count = 0l;
    try (BufferedReader reader = new BufferedReader(new FileReader(this.file))){
        Set<File> fileSet = ConcurrentHashMap.newKeySet();
        fileSet.add(this.file);
        Stream<String> lineStream = reader.lines();
        lineStream.forEach(line -> {
            String[] words = line.split(" ");
            for(String word: words){
                //count = count.add(BigInteger.ONE);
                BiFunction<Set<File>, Set<File>, Set<File>> reMappingFunction = (Set<File> oldSet, Set<File> newSet) -> {
                    oldSet.addAll(newSet);
                    return oldSet;
                };
                System.out.println("Word " +word+" is in "+ this.file.getName());

                Problem5.stringToFileMap.merge(word, fileSet, reMappingFunction);
            }

        });
    }
    return count;
}
}

如上所述,我使用了BiFunction&#34; reMappingFunction&#34;与&#34;合并&#34; ConcurrentHashMap中的函数。

Problem5.java将从main函数调用,该函数使用文本文件的目录路径实例化它并调用它的&#34;执行&#34;方法。

import problem1.Problem1;
import problem3.Problem3;
import problem5.Problem5;

import java.util.concurrent.ExecutionException;

public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
    Problem5 problem5 = new Problem5("src/problem1");
    problem5.execute();
}
}

当我执行上述程序时,它有时会输出正确的输出,有时会输出错误的输出。

正确输出

objc[36588]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java (0x1064e64c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1065ae4e0). One of the two will be used. Which one is undefined.
Word Word1 is in file1.txt
Word Word1 is in file2.txt
Word Word2 is in file2.txt
Word Word1 is in file3.txt
Word Word2 is in file3.txt
Word Word3 is in file3.txt
Word Word1 is in file4.txt
Word Word2 is in file4.txt
Word Word3 is in file4.txt
Word Word4 is in file4.txt
Word4 is present in files file4.txt, 
Word2 is present in files file4.txt, file2.txt, file3.txt, 
Word3 is present in files file4.txt, file3.txt, 
Word1 is present in files file4.txt, file1.txt, file2.txt, file3.txt, 
Hashmap size = 4

Process finished with exit code 0

输出错误

objc[36672]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java (0x10c8f04c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10c9b84e0). One of the two will be used. Which one is undefined.
Word Word1 is in file2.txt
Word Word1 is in file1.txt
Word Word2 is in file2.txt
Word Word1 is in file3.txt
Word Word2 is in file3.txt
Word Word3 is in file3.txt
Word Word1 is in file4.txt
Word Word2 is in file4.txt
Word Word3 is in file4.txt
Word Word4 is in file4.txt
Word4 is present in files file4.txt, 
Word2 is present in files file4.txt, file2.txt, file1.txt, file3.txt, 
Word3 is present in files file4.txt, file3.txt, 
Word1 is present in files file4.txt, file2.txt, file1.txt, file3.txt, 
Hashmap size = 4

Process finished with exit code 0

正如您在上面的错误输出中看到的那样,问题5.java中并发hashmap的输出是错误的,而StringToFileMapper.java中sysout的输出是正确的。

我无法弄清楚为什么以上程序有时不起作用。

2 个答案:

答案 0 :(得分:0)

我找到了解决这个问题的方法。合并方法的BiFunction参数必须返回一个新集而不是修改现有集。正确&#34; BiFunction&#34;论点是:

BiFunction<Set<File>, Set<File>, Set<File>> reMappingFunction = (Set<File> oldSet, Set<File> newSet) -> {
                    Set<File> temp = new HashSet<>();
                    temp.addAll(newSet);
                    temp.addAll(oldSet);
                    return temp;
                };

答案 1 :(得分:0)

让我们以真正的多线程方式使其更简单

post

一些输出演示:

@Test
public void testConcurrentHashMap() {
    File folder = new File("/Users/lhearen/programs/JavaSe8Solutions/src/test/chap1");
    Map<String, Set<File>> wordFileMap = new ConcurrentHashMap<>();
    Arrays.stream(folder.listFiles()).parallel().forEach(file -> {
        out.println(file.getPath());
        out.println(Thread.currentThread().getName());
        List<String> words = readWordsFromFilePath(file.getPath());
        words.stream().forEach(word -> wordFileMap.merge(word, new HashSet<>(), (oldSet, newSet) -> {
            newSet.addAll(oldSet);
            return newSet;
        }).add(file));
    });
    wordFileMap.entrySet().forEach(entry -> {
        out.println("\"" + entry.getKey() + "\" in " + entry.getValue().size() + " file(s)");
    });
}