如何在Java中禁用System.out以提高速度

15

我正在用Java编写一个模拟重力的程序,在程序中有很多日志语句(使用System.out输出)。我的程序运行速度非常慢,我认为日志记录可能是其中的原因之一。是否有办法禁用System.out,使得我的程序不会在打印日志时变慢?或者我必须手动去逐个注释调试语句来启用/禁用它们?


我想你可以创建一个流吞噬器,将标准输出流重定向到你的吞噬器,然后从那里开始。 - Hovercraft Full Of Eels
1
不重构你的代码,我表示怀疑。 - Alexander
@你试过向/dev/null(如果你在UNIX系统上)或者NUL(如果你在Windows系统上)写入数据吗?这样应该会更快一些,虽然具体能提升多少速度我不确定。但我预计它会显著加快速度。 - Jayan
2
System.out并不是(正如您已经发现的那样)一个记录API;请考虑使用java.util.logging或其他众多替代方案。 - McDowell
10个回答

24

可以再次使用输出流吞噬器,类似于以下内容...

System.setOut(new PrintStream(new OutputStream() {
    @Override
    public void write(int arg0) throws IOException {

    }
}));

但更好的解决方案是使用已经提到过的正式日志记录工具。


1
我想过这个,但我认为当PrintStream格式化输入流到OutputStream时,仍然会使用一些时间。 - Gavin S. Yancey
Logback和其他SLF4J风格的接口为这个问题提供了一个不错的解决方案 - 所谓的“{}”调用风格。请参阅此链接:http://www.slf4j.org/faq.html#logging_performance。 - sparc_spread

13
你应该使用像log4jslf4j这样的真正日志框架。在任何情况下,你都可以设置日志级别来限制被转储到日志中的信息级别。

如果你坚持使用System.out,你可以将日志语句包装在条件语句中,像这样:

if (DEBUG) {
  System.out.println(...);
}

是的,大量日志记录或向System.out打印信息会影响性能。


为什么你说没有办法关闭System.out,实际上它可以被重新定向? - Hovercraft Full Of Eels
@HovercraftFullOfEels 你只能将它重定向到PrintStream,这将浪费几乎相同的时间来格式化它。 - Gavin S. Yancey
同意。原始问题是“禁用System.out”。从技术上讲,您无法禁用它。重定向它可能会减少负载,但仍需要编写流和评估要写入的字符串对象的工作。 - Jeffrey Blattman

9

我同意其他人的看法,应该使用适当的记录器。然而并非每种情况都可以这样做。如下代码禁用了输出,并且像OP要求的那样速度很快:

System.setOut(new java.io.PrintStream(new java.io.OutputStream() {
    @Override public void write(int b) {}
}) {
    @Override public void flush() {}
    @Override public void close() {}
    @Override public void write(int b) {}
    @Override public void write(byte[] b) {}
    @Override public void write(byte[] buf, int off, int len) {}
    @Override public void print(boolean b) {}
    @Override public void print(char c) {}
    @Override public void print(int i) {}
    @Override public void print(long l) {}
    @Override public void print(float f) {}
    @Override public void print(double d) {}
    @Override public void print(char[] s) {}
    @Override public void print(String s) {}
    @Override public void print(Object obj) {}
    @Override public void println() {}
    @Override public void println(boolean x) {}
    @Override public void println(char x) {}
    @Override public void println(int x) {}
    @Override public void println(long x) {}
    @Override public void println(float x) {}
    @Override public void println(double x) {}
    @Override public void println(char[] x) {}
    @Override public void println(String x) {}
    @Override public void println(Object x) {}
    @Override public java.io.PrintStream printf(String format, Object... args) { return this; }
    @Override public java.io.PrintStream printf(java.util.Locale l, String format, Object... args) { return this; }
    @Override public java.io.PrintStream format(String format, Object... args) { return this; }
    @Override public java.io.PrintStream format(java.util.Locale l, String format, Object... args) { return this; }
    @Override public java.io.PrintStream append(CharSequence csq) { return this; }
    @Override public java.io.PrintStream append(CharSequence csq, int start, int end) { return this; }
    @Override public java.io.PrintStream append(char c) { return this; }
});

我的样本测量结果如下:

  • 完整输出需要 82 毫秒
  • 仅重写 OutputStream 中的 write(int) 方法需要 57 毫秒
  • 同时重写 PrintStream 的方法需要 31 毫秒
  • 当注释掉所有的 printlnprintf 使用时,只需要 5 毫秒

因此,它比其他答案更快,但无法击败不存在的代码(在 System.out 中加上注释或包装在 if (DEBUG) 中),因为可变参数会分配数组和封装基元类型,StringBuilder+ 也一样)依然会执行。


为什么这个在静态块内部不起作用呢? - 53by97
谢谢您提供的解决方案。现在我们如何在以后重新启用System.out呢? - rahul
@rahul stdout = System.out 然后调用 System.setOut(stdout) - TWiStErRob

3
一定要开始使用日志API而不是System.out。我强烈推荐LogbackLogback。您可以轻松设置仅记录到控制台但随时可以关闭的内容。Logback很容易配置,但如果您不喜欢配置文件,甚至可以通过编程方式设置。 Log4j也很好,它是同一作者早期的框架,被许多人认为是标准。SLF4J再次由同一作者创建,它不是真正的日志框架,而是一个抽象实际日志实现的标准包装器。Log4j可以被包装在SLF4J中,而Logback具有本地SLF4J接口。如果我是您,我会避免使用Apache Commons Logging,因为各种原因。我从来没有成为内置Java日志(java.util.logging)的铁杆粉丝。

2

我建议您在未来了解log4j(或类似的库)。它的工作方式与system.out相同,但实际上有开关可以在需要时关闭它们。


2

如果您的应用程序是在命令行中启动的,您可以在操作系统级别禁用整个程序的标准输出流。这将有效地禁用所有System.out.prinln(),而无需更改任何代码。

在Linux/Unix上,您可以通过将stdout重定向到/dev/null来关闭它:java MyJavaProgram > /dev/null


它是从Eclipse开始的。有没有办法在Eclipse中做到这一点? - Gavin S. Yancey
@+1。原始问题似乎不是特定于Eclipse的。这个答案应该在其他答案之上列出。(我错过了它,因此在nul上发表了评论) - Jayan
在Eclipse中,打开“运行配置”窗口,选择“常规”选项卡。在窗口底部有一个处理标准输入和输出的部分。取消选择“分配控制台”,它就不会再向您的控制台输出任何内容了。请注意,这将禁用您使用stdin的能力。 - shj

1

如果您不介意只需一次全部完成,您可以创建一个常量来“启用”或“禁用”,并添加一个标志以仅在启用该常量时打印。然后您可以轻松地翻转该常量。

public static boolean PRINTLN_ENABLED = true;

if(Constant.PRINTLN_ENABLED)
    System.out.println();

这个程序可以运行。但是它存在一个问题,就是你必须更改源代码中的常量并重新编译类以及每个使用该常量的其他类才能打开或关闭日志记录。如果您将常量替换为编译时不知道的内容,则每次都需要执行测试,这代表了一小部分运行时开销。 - Stephen C

1

0

我猜你可以使用System.setOut(PrintStream)来委托一个什么也不做的PrintStream,但这可能无法消除所有开销——例如,我猜错误消息的格式化仍然会花费你的时间。


你只能在 PrintStream 上使用 System.setOut。 - Gavin S. Yancey

0

我曾在我的Web项目中的查找监听器中放置了这段代码并解决了问题

  /*
     * In this case, if you are not in debug mode or any other 
     *the default system out is replaced internally with this
     *implementation, else it works as expected. This way you do not 
     *have to find and replace anything in your code.
     */
//or as i did servletContext.getInitParameter("mode") 
//from web.xml for global parameter

        if(!DEBUG) {
        System.setOut(
            new PrintStream(new OutputStream() { 
                public  void    close() {}
                public  void    flush() {}
                public  void    write(byte[] b) {}
                public  void    write(byte[] b, int off, int len) {}
                public  void    write(int b) {}

            } );
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接