Создать поток слов с помощью сканера - PullRequest
0 голосов
/ 20 ноября 2018

Необходимо вернуть поток всех слов, содержащих 3 буквы и более, из файла. Есть ли лучший способ, чем следующий, возможно, с помощью Stream.iterate:

private Stream<String> getWordsStream(String path){
    Stream.Builder<String> wordsStream = Stream.builder();
    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(path);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    Scanner s = new Scanner(inputStream);
    s.useDelimiter("([^a-zA-Z])");
    Pattern pattern = Pattern.compile("([a-zA-Z]{3,})");
    while ((s.hasNext())){
        if(s.hasNext(pattern)){
            wordsStream.add(s.next().toUpperCase());
        }
        else {
            s.next();
        }
    }
    s.close();
    return wordsStream.build();
}

Ответы [ 3 ]

0 голосов
/ 20 ноября 2018

Вы можете использовать Files.lines() и Pattern:

private static final Pattern SPACES = Pattern.compile("[^a-zA-Z]+");

public static Stream<String> getWordStream(String path) throws IOException{
    return Files.lines(Paths.get(path))
        .flatMap(SPACES::splitAsStream)
        .filter(word -> word.length() >= 3);
}
0 голосов
/ 20 ноября 2018

Худшая часть вашего кода - следующая часть

FileInputStream inputStream = null;
try {
    inputStream = new FileInputStream(path);
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
Scanner s = new Scanner(inputStream);

Таким образом, когда файл отсутствует, вы напечатаете трассировку стека FileNotFoundException и продолжите ввод потока null, что приведет к NullPointerException. Вместо того, чтобы требовать от вызывающей стороны иметь дело с ложным NullPointerException, вы должны объявить FileNotFoundException в сигнатуре метода. В противном случае верните пустой поток в ошибочном регистре.

Но вам вообще не нужно создавать FileInputStream, поскольку Scanner предлагает конструкторам, принимающим File или Path. Объедините это с возможностью возврата потока совпадений (начиная с Java 9), и вы получите:

private Stream<String> getWordsStream(String path) {
    try {
        Scanner s = new Scanner(Paths.get(path));
        return s.findAll("([a-zA-Z]{3,})").map(mr -> mr.group().toUpperCase());
    } catch(IOException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        return Stream.empty();
    }
}

или предпочтительно

private Stream<String> getWordsStream(String path) throws IOException {
    Scanner s = new Scanner(Paths.get(path));
    return s.findAll("([a-zA-Z]{3,})").map(mr -> mr.group().toUpperCase());
}

Вам даже не нужно .useDelimiter("([^a-zA-Z])") здесь, поскольку пропуск всех несоответствующих элементов является поведением по умолчанию.

Закрытие возвращенного Stream также закроет Scanner.

Так что вызывающий должен использовать это так

try(Stream<String> s = getWordsStream("path/to/file")) {
    s.forEach(System.out::println);
}
0 голосов
/ 20 ноября 2018

Намного проще подход: прочитать строки из файла в Stream и отфильтровать его с требуемым условием (например, длина> = 3).Files.lines() имеет ленивую загрузку, поэтому он не готовит все слова из файла в начале, он делает это каждый раз, когда требуется следующее слово

public static void main(String... args) throws IOException {
    getWordsStream(Paths.get("d:/words.txt")).forEach(System.out::println);
}

public static Stream<String> getWordsStream(Path path) throws IOException {
    final Scanner scan = new Scanner(path);

    return StreamSupport.stream(new Spliterators.AbstractSpliterator<String>(Long.MAX_VALUE,
            Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED) {
        @Override
        public boolean tryAdvance(Consumer<? super String> action) {
            while (scan.hasNext()) {
                String word = scan.next();

                // you can use RegExp if you have more complicated condition
                if (word.length() < 3)
                    continue;

                action.accept(word);
                return true;
            }

            return false;
        }
    }, false).onClose(scan::close);
}
...