Java 8 ConcurrentHashMap 合并与 computeIfAbsent

Java 8 ConcurrentHashMap merge vs computeIfAbsent

我正在做 Cay S. Horstmann 的 "Java SE 8 for the Really Impatient" 一书中的练习。有 2 个练习要求同一算法的不同实现,一个使用 merge,另一个 computeIfAbsent。我已经使用 merge 实现了该程序,但无法弄清楚如何使用 computeIfAbsent 来做同样的事情。在我看来 computeIfPresent 会更合适,因为 merge 只有在密钥存在时才有效, computeIfPresent.

也是如此

问题陈述:

Write an application in which multiple threads read all words from a collection of files. Use a ConcurrentHashMap<String, Set<File>> to track in which files each word occurs. Use the merge method to update the map.

我的代码使用 merge:

public static Map<String, Set<File>> reverseIndexUsingMerge(final Path path)
        throws IOException {
    final ConcurrentHashMap<String, Set<File>> map = new ConcurrentHashMap<>();

    final BiConsumer<? super String, ? super Set<File>> action = (key,
        value) -> map.merge(key, value, (existingValue, newValue) -> {
        LOGGER.info("Received key: {}, existing value: {}, new value: {}.",
            key, existingValue, newValue);

        newValue.addAll(existingValue);

        return newValue;
    });

    commonPool().invokeAll(
        find(path, 1,
            (p, fileAttributes) -> fileAttributes.isRegularFile())
            .map(p -> new ReverseIndex(p, action))
            .collect(toList()));

    return unmodifiableMap(map);
}

private static class ReverseIndex implements Callable<Void> {
    private final Path p;
    private final BiConsumer<? super String, ? super Set<File>> action;

    private static final Pattern AROUND_WHITESPACE = compile("\s");

    private ReverseIndex(final Path p,
        final BiConsumer<? super String, ? super Set<File>> action) {
        this.p = p;
        this.action = action;
    }

    @Override
    public Void call() throws Exception {
        reverseIndex().forEach(action);

        return null;
    }

    private Map<String, Set<File>> reverseIndex() {
        /* File stream needs to be closed. */
        try (Stream<String> lines = lines(p, UTF_8)) {
        return lines.flatMap(AROUND_WHITESPACE::splitAsStream)
            .collect(
                groupingBy(String::toString,
                    mapping(word -> p.toFile(), toSet())));
        } catch (IOException e) {
        LOGGER.error("Something went wrong. Get the hell outta here.",
            e);

        throw new UncheckedIOException(e);
        }
    }
}

专注于必须完成的事情,如果价值不存在。您需要做的是为缺失的条目创建一个新的 Set 值。当并发Set。您可以利用 ConcurrentHashMap 通过映射到固定值来创建事实上的 ConcurrentHashSet (该形式不存在),如果您让值信号存在是 Boolean.TRUE:

ConcurrentHashMap<String, Set<File>> map=new ConcurrentHashMap<>();
final BiConsumer<? super String, ? super Set<File>> action =
    (key, value) -> map.computeIfAbsent(key, x->ConcurrentHashMap.newKeySet())
                       .addAll(value);

我使用 computeIfAbsent 并按扩展名“.txt”过滤文件。 结果显示在控制台中。 对于IntelliJ IDEA IDE:如果结果不完整,则检查并增加"Override console cycle buffer size"(File/Settings/Editor/General/Console).

导入列表:

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

我解决这个任务:

public static void printMap(Path path){
        try (Stream<Path> stream = Files.walk(path)){
            ConcurrentHashMap<String, Set<File>> map = new ConcurrentHashMap<>();
            stream.parallel()
                    .filter(p -> !(Files.isDirectory(p)) & p.getFileName()
                            .toString()
                            .toLowerCase()
                            .endsWith(".txt"))
                    .collect(Collectors.toList())
                    .forEach((p) -> {
                        try {
                            Files.lines(p, StandardCharsets.UTF_8)
                                    .flatMap(s -> Arrays.asList(s.split("\PL+")).stream())
                                    .filter(w -> w.length() > 0)
                                    .map(String::toLowerCase)
                                    .parallel()
                                    .forEach(
                                            key -> {
                                                Set<File> tempSet = new HashSet<>();
                                                tempSet.add(new File(p.toString()));
                                                map.computeIfAbsent(key, x -> ConcurrentHashMap.newKeySet())
                                                        .addAll(tempSet);
                                            });
                        } catch (IOException e){
                        } catch (UncheckedIOException e){}
                    });

            map.entrySet().stream()
                    .sorted(Map.Entry.comparingByKey())
                    .forEach(System.out::println);

        } catch (IOException e){}
    }

通话printMap():

public static void main(String[] args){
        Path path = Paths.get(*somePathName*);
        printMap(path);
    }

如果需要使用merge,那么只需替换

map.computeIfAbsent(key, x -> ConcurrentHashMap.newKeySet()).addAll(tempSet);

map.merge(key, tempSet, (oldSet, newSet) -> {oldSet.addAll(newSet); return oldSet;});