我使用一个API(有关更多信息,请参见下文),该API接受OutputStream来捕获数据。相反,我想提供Strings的Consumer,它会逐行消耗数据。因此,我必须编写一个包装此类Consumer的OutputStream实现。这是我最容易想到的:
import java.io.OutputStream;
import java.util.Objects;
import java.util.function.Consumer;
public class OutputStreamConsumer extends OutputStream {
private final Consumer<String> consumer;
private final StringBuilder sb = new StringBuilder();
public OutputStreamConsumer(Consumer<String> consumer) {
this.consumer = Objects.requireNonNull(consumer);
}
@Override
public void write(int b) {
char c = (char) b;
if (c == '\r') {
return;
}
if (c == '\n') {
consume();
return;
}
this.sb.append(c);
}
@Override
public void close() {
if (sb.length() != 0) {
consume();
}
}
private void consume() {
this.consumer.accept(this.sb.toString());
this.sb.delete(0, Integer.MAX_VALUE);
}
}
但是,就encoding和performance而言,这对于生产代码而言可能还不够。我认为必要的逻辑已经包含在InputStreamReader和BufferedReader中,但是在这种情况下我无法使用这些类。编写这种OutputStream的最佳方法是什么?我可以使用哪些jdk类来避免编写一堆低级代码来处理编码,行尾等。
具体用例
[在Gradle插件项目中,我使用Gradle的项目API:ExecSpec启动了一个外部过程。在这里,我可以使用方法setStandardOutput和setErrorOutput设置OutputStreams,以捕获进程的输出。
由于到目前为止还没有答案,因此我将在撰写本文时发布“最佳”解决方案。随时发布更好的解决方案。
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
public class LineReadingOutputStream extends OutputStream {
private static final byte CR = '\r';
private static final byte LF = '\n';
private final Consumer<String> consumer;
private final StringBuilder stringBuilder = new StringBuilder();
private boolean lastCR = false;
private LineReadingOutputStream(final Consumer<String> consumer) {
this.consumer = Objects.requireNonNull(consumer);
}
@Override
public void write(final int b) throws IOException {
write(new byte[]{(byte) b});
}
@Override
public void write(final byte[] b, int start, final int len) {
if (b == null) {
throw new NullPointerException();
}
if (len < 0) {
throw new IllegalArgumentException();
}
final int end = start + len;
if ((start < 0) || (start > b.length) || (end < 0) || (end > b.length)) {
throw new IndexOutOfBoundsException();
}
if (this.lastCR && start < end && b[start] == LF) {
start++;
this.lastCR = false;
} else if (start < end) {
this.lastCR = b[end - 1] == CR;
}
int base = start;
for (int i = start; i < end; i++) {
if (b[i] == LF || b[i] == CR) {
final String chunk = asString(b, base, i);
this.stringBuilder.append(chunk);
consume();
}
if (b[i] == LF) {
base = i + 1;
} else if (b[i] == CR) {
if (i < end - 1 && b[i + 1] == LF) {
base = i + 2;
i++;
} else {
base = i + 1;
}
}
}
final String chunk = asString(b, base, end);
this.stringBuilder.append(chunk);
}
@Override
public void close() {
if (this.stringBuilder.length() != 0) {
consume();
}
}
private static String asString(final byte[] b, final int start, final int end) {
if (start > end) {
throw new IllegalArgumentException();
}
if (start == end) {
return "";
}
final byte[] copy = Arrays.copyOfRange(b, start, end);
return new String(copy, StandardCharsets.UTF_8);
}
private void consume() {
this.consumer.accept(this.stringBuilder.toString());
this.stringBuilder.delete(0, Integer.MAX_VALUE);
}
}