使用Webflux的非阻塞FileWalkTree文件搜索

时间:2019-07-13 11:43:36

标签: java

我需要创建一个非阻塞功能,在其中搜索给定文件夹中的文本文件,并返回其中找到的搜索词的数量。

我能够以阻塞的方式执行测试。我想知道是否有人可以帮助我将其转换为非阻塞任务,以便每当扫描完文件后,都无需等待所有文件的扫描就可以交付结果。

主要要点是:我不想等待所有文件被扫描以开始将结果传送到客户端(Angular应用程序)。

public interface SearchService {
    List<SearchResponse> search(SearchRequest searchRequest);
}
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

@ToString
@Getter
@RequiredArgsConstructor(staticName = "of")
public class SearchResponse implements Serializable {

    private final String server;
    private final String filePath;
    private final long count;
    private final boolean errorReadingFile;
}

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;

import javax.validation.constraints.NotNull;
import java.io.Serializable;


@ToString
@Getter
@RequiredArgsConstructor(staticName = "of")
public class SearchRequest implements Serializable {
    @NotNull
    private final String server;
    @NotNull
    private final String rootPath;
    @NotNull
    private final String searchTerm;
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;


@Slf4j
@Service
public class FileSearchService implements SearchService {

    @Override
    public List<SearchResponse> search(SearchRequest searchRequest) {
        Path start = Paths.get(searchRequest.getRootPath());
        EnumSet<FileVisitOption> opts = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
        int maxDepth = Integer.MAX_VALUE;
        SearchTermFileVisitor visitor = new SearchTermFileVisitor(searchRequest, new ArrayList<>());
        try {
            Files.walkFileTree(start,opts,maxDepth, visitor);
            return visitor.getSearchResponseList();
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }

}
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

@Slf4j
@Getter
@RequiredArgsConstructor
public class SearchTermFileVisitor extends SimpleFileVisitor<Path> {
    private final SearchRequest searchRequest;
    private final List<SearchResponse> searchResponseList;

    private SearchResponse searchFileContent(Path path, SearchRequest searchRequest) {
        SearchResponse response;
        try (BufferedReader br = Files.newBufferedReader(path)) {
            response = SearchResponse.of(
                    searchRequest.getServer(),
                    Paths.get(path.toUri()).toString(),
                    countWordsInFile(searchRequest.getSearchTerm(), br.lines()),
                    false);
        } catch (Exception e) {
            response = SearchResponse.of(
                    searchRequest.getServer(),
                    path.toString(),
                    0,
                    true);
        }
        log.debug(response.toString());
        return response;
    }

    private int countWordsInFile(String searchTerm, Stream<String> linesStream) {
        return linesStream
                .parallel()
                .map(line -> countWordsInLine(line, searchTerm))
                .reduce(0, Integer::sum);
    }

    private int countWordsInLine(String line, String searchTerm) {
        Pattern pattern = Pattern.compile(searchTerm.toLowerCase());
        Matcher matcher = pattern.matcher(line.toLowerCase());

        int count = 0;
        int i = 0;
        while (matcher.find(i)) {
            count++;
            i = matcher.start() + 1;
        }
        return count;
    }

    private boolean isTextFile(Path path) throws IOException {
        String type = Files.probeContentType(path);
        if (type == null) {
            //type couldn't be determined, assume binary
            return false;
        } else if (type.startsWith("text")) {
            return true;
        } else {
            //type isn't text
            return false;
        }
    }

    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        log.debug("Visited: " + (Path) dir);
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        if (attrs.isRegularFile()
                && !attrs.isDirectory()
                && !attrs.isSymbolicLink()
                && isTextFile(file)) {
            searchResponseList.add(searchFileContent(file, searchRequest));
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        return FileVisitResult.CONTINUE;
    }
}

测试案例:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.List;

class FileSearchServiceTest {

    private SearchService searchService = new FileSearchService();

    @Test
    void test_search_window_root_c_path() {
        SearchRequest sr = SearchRequest.of("localhost", "c:\\", "a");
        final List<SearchResponse> searchResult = searchService.search(sr);
        Assertions.assertNotNull(searchResult.size());
    }
}

我想使用WebFlux一次接收结果,而不必等待所有文件被扫描。

1 个答案:

答案 0 :(得分:0)

考虑(1)在单独的线程中执行搜索;(2)使用观察者模式将中间结果接收回该线程创建的代码(下面的参考);以及(3)将搜索线程加入到实现代码中完成后,您可以返回结果列表。这意味着您需要将“ this”传递给线程,以便它具有引用以调用单独的方法来获得中间结果。下面的参考包含示例代码。

像GUI这样考虑。您可以在单独的线程中运行GUI,对于每个按钮单击,它都将回调到控制器代码(单击“退出”后将包括“完成”)。

回复:https://dzone.com/articles/the-observer-pattern-using-modern-java https://en.wikipedia.org/wiki/Observer_pattern