我想在写入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);
}
在将数据写入输出流时,是否存在可以为我执行标记化的非阻塞实用程序?
答案 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
:
这些流位于已经存在的输出流(基础输出流)之上,该流用作基本数据接收器,但可能会沿途转换数据或提供其他功能。
至少对我来说,这听起来像您所描述的。
这是一个具体的类(不同于,因此您可以避免的绝对最小值是为单字节{{1}提供构造函数和实现}(将由其他OutputStream
)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);
}
}
}