Java OutputStream 读取字符串行

Java OutputStream reading lines of strings

我使用接受 OutputStream 的 API(有关详细信息,请参见下文)来捕获数据。相反,我想提供一个 Consumer of Strings 来逐行消耗数据。因此,我必须编写一个包装此类消费者的 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);
    }

}

但是,就 编码 性能 而言,这对于生产代码而言可能还不够。我认为必要的逻辑已经包含在 InputStreamReaderBufferedReader 中,但我不能在这种情况下使用这些 类。 编写这种 OutputStream 的最佳方法是什么?我可以使用什么 jdk 类 来避免编写一堆低级代码处理编码、行尾等

具体用例

在 Gradle 插件项目中,我使用 Gradle 的项目 API: ExecSpec 启动了一个外部进程。我可以使用 setStandardOutputsetErrorOutput 方法设置 OutputStreams 以捕获进程的输出。

由于到目前为止还没有答案,我将 post 我的 "best" 解决方案作为写作的时间。欢迎 post 更好的解决方案。

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