我该缓存System.getProperty("line.separator")吗?

11

考虑以下方法:

@Override
public String toString()
{
    final StringBuilder sb = new StringBuilder();
    for (final Room room : map)
    {
        sb.append(room.toString());
        sb.append(System.getProperty("line.separator")); // THIS IS IMPORTANT
    }
    return sb.toString();
}

System.getProperty("line.separator") 可以被调用多次。

我应该使用 public final static String lineSeperator = System.getProperty("line.separator") 缓存此值,然后仅使用 lineSeperator 吗?

或者 System.getProperty("line.separator") 和使用静态字段一样快吗?


我认为System.getProperty应该在每次调用时查找指定的键。我不知道编译器是否进行了任何优化。让我们看看人们建议什么。 - Fedy2
你是在使用Java 7或更早版本吗? - chrylis -cautiouslyoptimistic-
@chrylis 我正在使用6,考虑从7开始(到目前为止我没有看到升级的强烈理由...)。在这个 getProperty() 中,6和7有什么区别吗? - Adam Stelmaszczyk
是的,7将此特定问题升级为System.lineSeparator()。如果您已经在使用它,可能不值得升级,但这是一个更简单的答案。 - chrylis -cautiouslyoptimistic-
5个回答

3
如果您已经意识到与此相关的性能问题,是的。
如果没有,那么查找可能不会有足够的开销来影响性能。
这将属于“微观优化”和/或“过早优化”的一般类别。:-)
但是,如果您担心效率,您可能有一个 更大 的机会,即您的 toString 方法每次都在重新生成字符串。如果 toString 会被频繁调用,而不是缓存行终止符,缓存生成的字符串,并在您的房间地图发生更改时清除它。例如:
@Override
public String toString()
{
    if (cachedString == null)
    {
        final StringBuilder sb = new StringBuilder();
        final String ls = System.getProperty("line.separator");
        for (final Room room : map)
        {
            sb.append(room.toString());
            sb.append(ls);
        }
        cachedString = sb.toString();
    }
    return cachedString;
}

当你的地图发生变化时,请执行以下操作:

cachedString = null;

这会让你的投入更有回报(投入指的是添加额外字段的开销)。虽然这是每个实例而不是每个类,所以只有当你有充分理由时才这样做(参考前面的关于效率的评论)。


1
过早的优化可能会适得其反,当它导致“糟糕”的代码时就是这种情况。但在这种情况下,代码将更快且可读性更高(如果不是更高),因此我认为它并不属于那个类别... - assylias
2
@assylias:首先,让我说一句:很少有人比我更容易过早优化了。 :-) 但是 PO 并不仅仅是关于创建糟糕的代码。它是关于花费时间、精力和内存去优化那些不需要优化的东西,当你可以利用这些时间、精力和内存去优化那些需要优化或增加功能的东西时。 - T.J. Crowder
@T.J.Crowder 我没有任何反对意见,但在这种特殊情况下,我会提取本地变量,而性能甚至不会进入我的考虑范围。 - Marko Topolnik
@MarkoTopolnik:在函数中使用本地变量没有问题。但是,OP谈到了使用“static”,请参见对Alex MDC的评论。 - T.J. Crowder
是的,我刚刚在我的答案中添加了这个。OP只是提出了一个错误的二分法:每次调用或创建静态字段。 - Marko Topolnik
1
这是最好的答案。 - ACV

3

我认为你的问题存在一个错误的二分法。我既不会每次都调用getProperty,也不会为它声明一个静态字段。我会在toString函数中将其提取为一个局部变量。

@Override
public String toString()
{
    final StringBuilder sb = new StringBuilder();
    final String newline = System.getProperty("line.separator"); 
    for (final Room room : map) sb.append(room.toString()).append(newline);
    return sb.toString();
}

顺便说一句,我已经对这个调用进行了基准测试。代码如下:

public class GetProperty
{
  static char[] ary = new char[1];
  @GenerateMicroBenchmark public void everyTime() {
    for (int i = 0; i < 100_000; i++) ary[0] = System.getProperty("line.separator").charAt(0);
  }
  @GenerateMicroBenchmark public void cache() {
    final char c = System.getProperty("line.separator").charAt(0);
    for (int i = 0; i < 100_000; i++) ary[0] = (char)(c | ary[0]);
  }
}

结果如下:
Benchmark                     Mode Thr    Cnt  Sec         Mean   Mean error    Units
GetProperty.cache            thrpt   1      3    5       10.318        0.223 ops/msec
GetProperty.everyTime        thrpt   1      3    5        0.055        0.000 ops/msec

缓存方法比直接操作快上两个数量级。

需要注意的是,对于所有字符串构建的getProperty调用的总体影响非常非常不可能被注意到。


不是一个好的答案,如果缓存更快,你应该将其缓存为静态变量。如果调用 toString 多次会怎样? - Enerccio
系统属性可以在调用之间更改。特别是,用户可能只设置一次,但在此类已初始化后。另一方面,如果映射中的项目数量非常小,则静态缓存仅通过每次toString调用赢得一次访问。您不能通过提到与此不同的权衡的替代方案来声称这不是“不好的答案”。 - Marko Topolnik
1
为什么System.getProperty("line.separator")会经常变化?如果行分隔符在调用之间改变,那么任何toString()的结果都将难以理解。 - lifesoordinary

3

您不必担心在代码运行时换行符会发生变化,因此我认为没有理由反对对其进行缓存。

缓存一个值肯定比反复执行调用要快,但是差别可能微不足道。


1
取决于调用是否在热点优化期间内联 :) - William Morrison
1
哦,好的观点。但我个人不喜欢依赖这样的功能 - 有没有代码已经看起来很有效? - MightyPork
我完全同意!太多时候,同行开发人员用在运行时会修复的借口来掩盖诚实的低效率。只是提出一个观点。 - William Morrison
1
@WilliamMorrison 在线调用不会有任何影响,因为大部分时间都花费在getProperty方法内部。如果你的意思是JIT可能会内联从该方法返回的,我会说这是极不可能的,我的基准测试证实了这一点,适用于OpenJDK 7。 - Marko Topolnik
后者是我想表达的意思。是的,我看到了你的基准测试,做得很好。我一定是搞错了。 - William Morrison
@MightyPork 我不同意,代码的可读性和可维护性比优化更重要(通常优化意味着代码难以阅读和维护)。JIT优化使我们能够编写高效且良好编写的面向对象代码(例如始终使用getter访问所有实例变量而不是直接引用它们,因为知道get调用将被内联)。如果我们有一个功能可以拥有易于阅读、易于维护和优化的代码,为什么不利用它呢? - SmokeIT

2
既然这么容易,为什么不这样做呢?至少实现System.getProperty()需要进行哈希表查找(即使在内部缓存),以找到您请求的属性,然后将调用结果对象上的虚拟方法getString()。这些操作都不算非常昂贵,但需要多次调用。更不用说会创建许多String临时变量,并在之后需要进行垃圾回收。
如果将此移到循环顶部并重复使用相同的值,就可以避免所有这些问题。那为什么不这样做呢?

4
“既然这么容易,为什么不呢?” 因为如果没有必要,创建额外的静态字段是一个坏主意。每个使用任何不变系统属性的类都应该保留自己的副本吗?以及其他不变的东西?当没有明显的性能提升时呢?不应该。那只会使内存混乱,并填充重复信息。 - T.J. Crowder
2
我同意,我的意思是在进入循环之前,在函数内部计算一次。我没有注意到 OP 建议使用静态值。 - Alex MDC
@ alex:啊,是的,那就不一样了。 :-) - T.J. Crowder

0
如果系统属性在应用程序期间保证不变,则可以将其缓存,但通常情况下,当更改属性时,您将失去更改行为的属性功能。
例如,文本生成器可以使用该属性为Windows或Linux生成文本,并允许在应用程序中动态更改该属性,为什么不呢?
一般来说,捕获属性意味着使函数setProperty无用。

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