如何将 Java 8 Streams 与 InputStream 一起使用?

2022-09-02 13:05:20

我想围绕一个来处理一个字节或一个角色。我没有找到任何简单的方法来做到这一点。java.util.streams.StreamInputStream

请考虑以下练习:我们希望计算每个字母在文本文件中出现的次数。我们可以将其存储在数组中,以便存储a在文件中出现的次数,存储b出现的时间次数等等。由于我找不到直接流式传输文件的方法,因此我这样做了:tally[0]tally[1]

 int[] tally = new int[26];
 Stream<String> lines = Files.lines(Path.get(aFile)).map(s -> s.toLowerCase());
 Consumer<String> charCount = new Consumer<String>() {
   public void accept(String t) {
      for(int i=0; i<t.length(); i++)
         if(Character.isLetter(t.charAt(i) )
            tall[t.charAt(i) - 'a' ]++;
   }
 };
 lines.forEach(charCount);

有没有办法在不使用该方法的情况下实现这一目标?我是否可以直接将每个字符作为流或流处理,而不是为文本文件中的每一行创建字符串。lines

我可以更直接地转换为?java.io.InputStreamjava.util.Stream.stream


答案 1

首先,你必须重新定义你的任务。您正在读取字符,因此您不想将 a 但 a 转换为 .InputStreamReaderStream

您无法重新实现发生的字符集转换,例如在 中使用 操作,因为在 s 和生成的 s 之间可能存在 n:m 映射。InputStreamReaderStreambyteInputStreamchar

从 中创建流有点棘手。您将需要一个迭代器来指定用于获取项目的方法和结束条件:Reader

PrimitiveIterator.OfInt it=new PrimitiveIterator.OfInt() {
    int last=-2;
    public int nextInt() {
      if(last==-2 && !hasNext())
          throw new NoSuchElementException();
      try { return last; } finally { last=-2; }
    }
    public boolean hasNext() {
      if(last==-2)
        try { last=reader.read(); }
        catch(IOException ex) { throw new UncheckedIOException(ex); }
      return last>=0;
    }
};

拥有迭代器后,您可以使用拆分器的绕行符创建流并执行所需的操作:

int[] tally = new int[26];
StreamSupport.intStream(Spliterators.spliteratorUnknownSize(
  it, Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL), false)
// now you have your stream and you can operate on it:
  .map(Character::toLowerCase)
  .filter(c -> c>='a'&&c<='z')
  .map(c -> c-'a')
  .forEach(i -> tally[i]++);

请注意,虽然迭代器更熟悉,但实现新接口直接简化了操作,因为它不需要维护可以按任意顺序调用的两个方法之间的状态。相反,我们只有一个可以直接映射到调用的方法:SpliteratortryAdvanceread()

Spliterator.OfInt sp = new Spliterators.AbstractIntSpliterator(1000L,
    Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL) {
        public boolean tryAdvance(IntConsumer action) {
            int ch;
            try { ch=reader.read(); }
            catch(IOException ex) { throw new UncheckedIOException(ex); }
            if(ch<0) return false;
            action.accept(ch);
            return true;
        }
    };
StreamSupport.intStream(sp, false)
// now you have your stream and you can operate on it:
…

但是,请注意,如果您改变主意并愿意使用,则可以过上更轻松的生活:Files.lines

int[] tally = new int[26];
Files.lines(Paths.get(file))
  .flatMapToInt(CharSequence::chars)
  .map(Character::toLowerCase)
  .filter(c -> c>='a'&&c<='z')
  .map(c -> c-'a')
  .forEach(i -> tally[i]++);

答案 2

推荐