Java控制台输出重定向到JavaFx的TextArea并且实现滚动输出保留固定行数

说明

用JavaFX写了个工具, 想将控制台的日志输出到一个文本域中, 同时控制显示的最大行数

实现

重定向

查看了logback的输出方式, 实际应该就是用的System.out, 具体查看ch.qos.logback.core.ConsoleAppender, 因此重定向到TextArea只需要覆盖System.out的原PrintStream, 然后将文本内容追加到文本域中

自定义一个输出流, 实现向文本域追加文本

重写write方法, 将文本内容转为字符串添加到文本域

@AllArgsConstructor(access = AccessLevel.PROTECTED)
private static class TextAreaOutputStream extends OutputStream {
    private final TextArea textArea;

    @Override
    public void write(int b) {
        Platform.runLater(() -> {
            textArea.appendText(String.valueOf((char) b));
            textArea.setScrollTop(Double.MAX_VALUE);
        });
    }

    @Override
    public void write(@Nonnull byte[] b, int off, int len) {
        Platform.runLater(() -> {
            textArea.appendText(new String(b, off, len));
            textArea.setScrollTop(Double.MAX_VALUE);
        });
    }
}

覆盖System.out流

public static TextArea wrap(TextArea textArea, Long maxLine) {
    // 关掉旧输出流
    IOUtils.closeQuietly(System.out);
    // 新的输出流
    final PrintStream nextPrintStream = new PrintStream(new TextAreaOutputStream(textArea), true);
    // 重定向流
    System.setOut(nextPrintStream);
    System.setErr(System.out);
    return textArea;
}

保留固定行数

控制文本域显示的最大行数, 即超过一定行数删除旧的行数

自定义文本内容监听器

实现一个工厂方法, 接收TextAreaLong(最大行数), 每次值变化后检查当前行数, 如果超过指定行数, 则只保留指定行数, 重新拼接后覆盖文本域原有内容

暂时想不到其他更有效率的方法, 应该可以考虑设计一个缓冲区, 指定一个有界队列, 先进先出, 超过指定深度后多于行数从头部poll出去, 但是在文本域中显示之前还是需要重新拼接, 感觉都一样

private static final BiFunction<TextArea, Long, ChangeListener<? super String>> lineClampListener = (t, l) -> (observable, oldValue, newValue) -> Platform.runLater(() -> {
    final String[] lines = newValue.split(StrUtil.LF);
    if (lines.length > l) {
        t.setText(
                IntStream.range(lines.length - l.intValue(), lines.length).collect(
                        StringBuilder::new,
                        (s, i) -> s.append(lines[i]).append(StrUtil.LF),
                        (s1, s2) -> {
                            // ignore
                        }
                ).toString()
        );
        t.setScrollTop(Double.MAX_VALUE);
    }
});

添加监听器到TextArea

textArea.textProperty().addListener(lineClampListener.apply(textArea, maxLine));

完整代码

public class ConsoleOutputRedirector {
    private static final BiFunction<TextArea, Long, ChangeListener<? super String>> lineClampListener = (t, l) -> (observable, oldValue, newValue) -> Platform.runLater(() -> {
        final String[] lines = newValue.split(StrUtil.LF);
        if (lines.length > l) {
            t.setText(
                    IntStream.range(lines.length - l.intValue(), lines.length).collect(
                            StringBuilder::new,
                            (s, i) -> s.append(lines[i]).append(StrUtil.LF),
                            (s1, s2) -> {
                                // ignore
                            }
                    ).toString()
            );
            t.setScrollTop(Double.MAX_VALUE);
        }
    });

    public static TextArea wrap(TextArea textArea) {
        // 默认不限制行数
        return wrap(textArea, Long.MAX_VALUE);
    }

    public static TextArea wrap(TextArea textArea, Long maxLine) {
        // 关掉旧输出流
        IOUtils.closeQuietly(System.out);
        // 控制行数的监听器
        textArea.textProperty().addListener(lineClampListener.apply(textArea, maxLine));
        // 新的输出流
        final PrintStream nextPrintStream = new PrintStream(new TextAreaOutputStream(textArea), true);
        // 重定向流
        System.setOut(nextPrintStream);
        System.setErr(System.out);
        return textArea;
    }

    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    private static class TextAreaOutputStream extends OutputStream {
        private final TextArea textArea;

        @Override
        public void write(int b) {
            Platform.runLater(() -> {
                textArea.appendText(String.valueOf((char) b));
                textArea.setScrollTop(Double.MAX_VALUE);
            });
        }

        @Override
        public void write(@Nonnull byte[] b, int off, int len) {
            Platform.runLater(() -> {
                textArea.appendText(new String(b, off, len));
                textArea.setScrollTop(Double.MAX_VALUE);
            });
        }
    }
}

使用

使用时指定一个输出的目标文本域即可

TextArea textArea = ConsoleOutputRedirector.wrap(new TextArea(), 100L);

效果

效果就是控制台所有内容都会输出到这个文本域, 同时最大只会显示指定行数

但是有一个问题, 如果控制台输出过快, 因为文本替换效率的问题, 会有明显卡顿, 过快是指并发输出大量日志, 上千上万条, 一般每个指定行数区间间隔个个别毫秒是没问题的

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值