【问题标题】:Create words' stream using scanner使用扫描仪创建单词流
【发布时间】:2018-11-19 21:43:16
【问题描述】:

需要从文件中返回包含 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();
}

【问题讨论】:

  • 哪个 Java 版本?
  • 您的意思是打电话给s.next(pattern)吗?
  • 也许将整个流作为字符串读取,然后用空格(或您使用的任何内容)分割它,然后检查每个流的长度。
  • Java 9。我的意思是:是否可以将这种方法更接近于流样式编写,而根本没有 while 循环

标签: java loops java-stream builder word


【解决方案1】:

代码中最糟糕的部分是以下部分

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 提供了接受 FilePath 的构造函数。将此与返回匹配流的功能(自 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);
}

【讨论】:

  • 对 105 K 字的书进行了测试。此方法耗时约 0.6 秒。
【解决方案2】:

您可以使用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);
}

【讨论】:

  • 对一本 105 K 字的书进行了测试。此方法最快,耗时 0.29s。
  • @Alex 好吧,这个方法也跳过了大写的转换。此外,您的原始代码旨在处理仅由 ASCII 字母组成的单词,而此代码将由单个空格字符分隔的所有内容视为一个单词。
  • Pattern 改为 Pattern SPACES = Pattern.compile("([^a-zA-Z])");
  • @Alex 您可能应该使用Pattern.compile("[^a-zA-Z]+")(注意末尾的+)。所以你不会得到“空”字,例如:"I have 100 dollars" 这样的文本会产生一个数组:["I", "have", "", "", "", "", "dollars"] 与当前模式
【解决方案3】:

有更简单的方法:从文件中读取行到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);
}

【讨论】:

  • lines() 将返回 单词 流?对于这种方法来说,这将是一个非常糟糕的名字——也许是 Java 9,但可以肯定的是,在 Java 11 中它会返回一个 lines
  • 它返回在任何 Java 中具有超过 3 个字母符号的行。我有一个未准备好的文本。所以需要再次循环来查找一行中的所有匹配项。
  • 好吧,实际上lines() 返回所有行,而不仅仅是超过 3 个字母的行。 getWordsStream() 返回包含 3 个或更多字母的行。但问题是关于 3 个或更多字母的 words,而不是关于 lines
  • 使用java-11,您无需更改该方法的签名,可以改用Files.lines(Path.of(path))
  • 已修复。相同的延迟加载方法。做个蛋糕。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-20
  • 2014-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多