Java OutputStream增量处理文本

时间:2019-04-24 18:50:30

标签: java outputstream

我想在写入OutputStream时逐步处理写入的文本。

例如,假设我们有这个程序:

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;

public class Streaming {

    // Writes file, incrementally, to OutputStream.
    static void dump(File file, OutputStream out) throws IOException {
        // Implementation omitted
    }

    static int sum = 0;
    public static void main(String[] args) throws IOException {
        Charset charSet = Charset.defaultCharset(); // Interpret the file as having this encoding.
        dump(new File("file.txt"), new OutputStream() {
            @Override
            public void write(int b) throws IOException {
                // Add b to bytes already read,
                // Determine if we have reached the end of the token (using
                //   the default encoding),
                // And parse the token and add it to `sum`
            }
        });
        System.out.println("Sum: " + sum);
    }
}

假设file.txt是一个文本文件,其中包含用空格分隔的整数列表。在此程序中,我希望在file.txt中找到整数的总和,并在sum变量中累加总和。我想避免建立一个长度为数百万个字符的字符串。

我对使用dump函数可以完成此操作的方式感兴趣,该函数将文件内容写入输出流。我 不希望以其他方式读取文件(例如,为Scanner创建file.txt并在扫描仪上反复调用nextInt)。我施加此限制是因为我使用的库具有类似于dump的API,客户端必须提供OutputStream,并且该库随后向输出流中写入大量文本

如何实现write方法以正确执行概述的步骤?我想避免手工进行令牌化,因为Scanner之类的实用程序已经能够进行令牌化,并且我希望能够处理文本的任何编码(由charSet指定)。但是,我无法直接使用Scanner,因为无法(以非阻塞方式)检查令牌是否可用:

    public static void main(String[] args) throws IOException {
        Charset charSet = Charset.defaultCharset();
        PipedInputStream in = new PipedInputStream();
        try (Scanner sc = new Scanner(in, charSet)) {
            dump(new File("file.txt"), new PipedOutputStream(in) {
                @Override
                public void write(byte[] b, int off, int len) throws IOException {
                    super.write(b, off, len);
                    // This will loop infinitely, because `hasNextInt`
                    // will block if there is no int token currently available.
                    if (sc.hasNextInt()) {
                        sum += sc.nextInt();
                    }
                }
            });
        }
        System.out.println("Sum: " + sum);
        System.out.println(charSet);
    }

在将数据写入输出流时,是否存在可以为我执行标记化的非阻塞实用程序?

3 个答案:

答案 0 :(得分:1)

如果我正确理解了您的问题,则FilterOutputStream是您要继承的内容。 DigestOutputStream扩展了FilterOutputStream,并做了一些与您想做的事情类似的事情:它监视字节通过时的字节,并将其传递给另一个类进行处理。

想到的一种解决方案是让FilterOutputStream将字节传递到PipedOutputStream,该https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString连接到PipedInputStream,另一个线程读取它以创建总和:

PipedOutputStream sumSink = new PipedOutputStream();

Callable<Long> sumCalculator = new Callable<Long>() {
    @Override
    public Long call()
    throws IOException {

        long sum = 0;
        PipedInputStream source = new PipedInputStream(sumSink);

        try (Scanner scanner = new Scanner(source, charSet)) {
            while (scanner.hasNextInt()) {
                sum += scanner.nextInt();
            }
        }

        return sum;
    }
};
Future<Long> sumTask = ForkJoinPool.commonPool().submit(sumCalculator);

OutputStream dest = getTrueDestinationOutputStream();
dest = new FilterOutputStream(dest) {
    @Override
    public void write(int b)
    throws IOException {
        super.write(b);
        sumSink.write(b);
    }

    @Override
    public void write(byte[] b)
    throws IOException {
        super.write(b);
        sumSink.write(b);
    }

    @Override
    public void write(byte[] b,
                      int offset,
                      int len)
    throws IOException {
        super.write(b, offset, len);
        sumSink.write(b, offset, len);
    }

    @Override
    public void flush()
    throws IOException {
        super.flush();
        sumSink.flush();
    }

    @Override
    public void close()
    throws IOException {
        super.close();
        sumSink.close();
    }
};

dump(file, dest);

long sum = sumTask.get();

答案 1 :(得分:0)

作为“惯用的”方法,您可能需要使用FilterOutputStream

  

这些流位于已经存在的输出流(基础输出流)之上,该流用作基本数据接收器,但可能会沿途转换数据或提供其他功能。

至少对我来说,这听起来像您所描述的。

这是一个具体的类(不同于OutputStream,因此您可以避免的绝对最小值是为单字节{{1}提供构造函数和实现}(将由其他write()方法的默认实现调用):

write()

这将对通过的所有数字求和,使public class SumOutputStream extends FilterOutputStream { public int sum = 0; public SumOutputStream(OutputStream os) { super(os); } private int num = 0; public void write(int b) throws IOException { if (b >= '0' && b <= '9') { sum -= num; num = num * 10 + b - '0'; sum += num; } else { num = 0; } out.write(b); } public static void main(String[] args) throws IOException { try (SumOutputStream sos = new SumOutputStream(new FileOutputStream("test.txt"))) { sos.write("123 456 78".getBytes()); System.out.println(sos.sum); sos.write('9'); System.out.println(sos.sum); } } } 始终保持最新,即使有部分结果(也就是分隔sum的结果)。

答案 2 :(得分:-1)

基于@tevemadar的回答。读取字符串并尝试将它们解析为int。如果失败,则您知道数字已完成,然后将其添加到总和中。唯一的问题是,如果我的方法占用了最后两个字节,则不会添加最后一个数字。要解决此问题,您可以添加单行方法:if(!currNumber.isEmpty()) sum += Integer.parseInt(currNumber);,一旦文件完成,便可以调用。

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;

class SumOutputStream extends FilterOutputStream {
  public int sum = 0;
  String currNumber = "";
  String lastChar = "";

  public SumOutputStream(OutputStream os){
    super(os);
  }

  public void write(byte b[], int off, int len) throws IOException {
      Objects.checkFromIndexSize(off, len, b.length);
      for (int i = 0 ; i < len ; i++) {
          try { 
              if(!lastChar.isEmpty()) {
                  Integer.parseInt(lastChar);
                  currNumber += lastChar;
              }
          } catch(NumberFormatException e) { 
              if(!currNumber.isEmpty()) sum += Integer.parseInt(currNumber);
              currNumber = "";
          }  catch(NullPointerException e) {
              e.printStackTrace();
          }
          write(b[off + i]);
          lastChar = new String(b);
      }
  }
}