如何将Java 8流与InputStream一起使用? [英] How can I use Java 8 Streams with an InputStream?

查看:260
本文介绍了如何将Java 8流与InputStream一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将java.util.streams.Stream包裹在InputStream周围,以一次处理一个字节或一个字符.我没有发现任何简单的方法.

I would like to wrap a java.util.streams.Stream around an InputStream to process one Byte or one Character at a time. I didn't find any simple way of doing this.

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

Consider the following exercise: We wish to count the number of times each letter appears in a text file. We can store this in an array so that tally[0] will store the number of times a appears in the file, tally[1] stores the number of time b appears and so on. Since I couldn't find a way of streaming the file directly, I did this:

 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方法即可完成此操作的方法?我可以直接将每个字符作为流或流直接处理,而不是为文本文件中的每一行创建字符串.

Is there a way of accomplishing this without using the lines method? Can I just process each character directly as a Stream or Stream instead of creating Strings for each line in the text file.

我可以直接将java.io.InputStream转换为java.util.Stream.stream吗?

Can I more direcly convert java.io.InputStream into java.util.Stream.stream ?

推荐答案

首先,您必须重新定义任务.您正在阅读字符,因此您不想将InputStream而是将Reader转换为Stream.

First, you have to redefine your task. You are reading characters, hence you do not want to convert an InputStream but a Reader into a Stream.

您无法重新实现发生的字符集转换,例如在InputStreamReader中,通过Stream操作,因为InputStreambyte与生成的char之间可能存在n:m映射.

You can’t re-implement the charset conversion that happens, e.g. in an InputStreamReader, with Stream operations as there can be n:m mappings between the bytes of the InputStream and the resulting chars.

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

Creating a stream out of a Reader is a bit tricky. You will need an iterator to specify a method for getting an item and an end condition:

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;
    }
};

一旦有了迭代器,就可以使用分离器的绕行创建流并执行所需的操作:

Once you have the iterator you can create a stream using the detour of a spliterator and perform your desired operation:

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]++);


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


Note that while iterators are more familiar, implementing the new Spliterator interface directly simplifies the operation as it doesn’t require to maintain state between two methods that could be called in arbitrary order. Instead, we have just one tryAdvance method which can be mapped directly to a read() call:

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,您的生活将会轻松得多


However, note that if you change your mind and are willing to use Files.lines you can have a much easier life:

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]++);

这篇关于如何将Java 8流与InputStream一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆