1

如果我要使用 javac 编译以下代码,编译器是否足够聪明,只能评估一次 codeInput.length(),或者我是否可以通过将受迭代器变量影响的评估放在首位来获得更快的程序?

// edit: The first character must be an upper case letter, the next four must be 
// edit: numeric and there must be exactly five characters in total in a product
// edit: code.

for (int i = 1; i < 5; i++)
    if (codeInput.length() != 5 || codeInput.charAt(i) < '0' 
        || codeInput.charAt(i) > '9' || codeInput.charAt(0) < 'A'
        || codeInput.charAt(0) > 'Z')
        {if (verbose) System.out.println("Sorry, I don't understand! 
            Use product codes only."); return true;}
4

6 回答 6

3

作为一项规则;您应该编写最简单、最清晰的代码,这样会很好地执行。通常,性能问题与使代码更清晰更重要。在你的例子中,我会这样写。

static final Pattern CODE = Pattern.compile("[A-Z][0-9]{4}");

if (!CODE.matcher(codeInput).matches()) {
    if (verbose) 
        System.out.println("Sorry, I don't understand! Use product codes only.");
    return true;
}

即使这稍微慢一些,维护恕我直言也更清晰,更容易


javac几乎没有优化,它们几乎都是由 JIT 在运行时执行的。

如果我用 javac 编译以下代码,编译器是否足够聪明,只计算一次 codeInput.length(),

不是,但,

或者我是否可以通过将受迭代器变量影响的评估放在首位来获得更快的程序?

这不像您会注意到差异,因为如果代码运行足够长的时间,JIT 就会启动,并且方法将被内联,从而不再需要局部变量。

于 2013-01-02T09:30:29.293 回答
2

您的第一个目标应该是代码的简单性和可读性。对于像您提出的问题这样简单的问题,您真的必须竭尽全力将其变成性能瓶颈。最简洁、最灵活的字符串验证方法是正则表达式:

boolean valid = codeInput.matches("[A-Z][0-9]{4}");
if (!valid && verbose) 
   System.out.println("Sorry, I don't understand! Use product codes only.");
return !valid;

如果你真的想要最后一点性能,你可以预编译正则表达式:

static final Pattern productCodeRegex = Pattern.compile("[A-Z][0-9]{4}");
boolean validate(String codeInput) {
  boolean valid = productCodeRegex.matcher(codeInput).matches();
  ...
}
于 2013-01-02T10:01:59.020 回答
1

是的,它将codeInput.length()在每次迭代中进行评估。codeInput如果是最终的或一次只能由一个线程访问,编译器可能能够进行优化。否则codeInput可以被当前线程不会注意到的另一个线程更改。

因此,程序员决定codeInput.length()跳出循环并利用提高的性能。

于 2013-01-02T09:29:50.567 回答
1

编译器必须创建codeInput.length()对每次迭代进行评估的代码,因为它不知道它是否会在每次迭代中返回相同的值。您知道这一点,因为您知道该课程的运作方式。但是编译器不知道它并且必须假设codeInput更改(例如由于调用charAt())。

所以是的:您可以通过评估codeInput.length()一次并将结果分配给局部变量来获得一些性能。

于 2013-01-02T09:30:30.397 回答
1

在这里聪明的不是编译器,而是JVM

现代 JVM 都具有 JIT(即时),这意味着代码将在旅途中进行优化。

不要费心尝试在 Java 中优化您的代码,只需编写正确的代码即可。JVM 将为您完成剩下的工作。

至于您的特定代码,如果您使用的是Guava,您可以CharMatcher在这里使用 a 来获得良好的效果:

private static final CharMatcher PRODUCT_CHARS
    = CharMatcher.inRange('A', 'Z')
        .or(CharMatcher.inRange('a', 'z'))
        .or(CharMatcher.inRange('0', '9'))
        .precompute();

// In the verification method:
if (!(codeInput.length() == 5 && PRODUCT_CHARS.matchesAllOf(codeInput)))
    badProductCode();
于 2013-01-02T09:37:12.667 回答
0

Marko 的答案显然是该问题中编程努力的最佳答案之一。

但是,将其用作一般示例,从中可以推断出没有更好的解决方案(如 regexp 或 CharMatcher)的上下文,作为风格和实践的问题,我使用并推荐更简单的表达式,捕捉原本会重复的内容使用局部变量的方法调用结果。

我断言这提高了清晰度,因为我们可以命名局部变量,并允许我们将每段更简单的代码定位在最合乎逻辑的位置以执行(同时还允许 JVM 优化它真正擅长的一件事,即局部变量用法)。

您会注意到,在这个转换后的版本中,我们在循环外测试 firstChar,而不是在循环内重复测试;与 .length() 相同。我首先会认为这是逻辑上更正确的编程(它让其他读者感到困惑,为什么这可能会在循环中一遍又一遍地完成),其次才是它会有更好的性能。

虽然循环的这种代码运动不会对这个简单示例的性能产生实质性影响(仅计算 5 次迭代,并且在使用循环库为我们提供更好的解决方案的情况下),但在一般情况下和其他情况下,我建议这样做最佳实践,更易读,更简洁(同时也是面向性能的)。

另请注意,我首先测试长度,因此 .charAt(0) 已知存在,即长度> 0,这意味着我们将reportError()使用零长度字符串,而不是抛出一些.charAt(0) IndexOutOfBounds。这是另一个示例,说明使用更简单的表达式如何允许编程逻辑的高级排序。

这种 KIS(保持简单)风格也使调试更容易,因为这些局部变量可以很容易地被观察到。我认为它对维护者来说也更具可读性。

此外,将常量 5 限制在一个位置可以简化维护。

public static final int expectedLength = 5;

...

if ( codeInput.length() != expectedLength )
    return reportError ();

char firstChar = codeInput.charAt(0);
if ( firstChar < 'A' || firstChar > 'Z' )
    return reportError (); 

for (int i = 1; i < expectedLength; i++) {
    char nextChar = codeInput.charAt(i);
    if ( nextChar < '0' || nextChar > '9' )
        return reportError ();
}

...

private static boolean reportError () {
    if (verbose) 
        System.out.println("Sorry, I don't understand!\nUse product codes only."); 
    return true;
}
于 2013-01-03T18:36:58.027 回答