2

我正在尝试基于 DB 生成的序列号 (Y) 创建一个经过良好优化的代码位来创建长度为 X 位的数字(其中 X 是从运行时属性文件中读取的),然后将其用于文件夹-name 保存文件时。

到目前为止,我已经提出了三个想法,其中最快的是最后一个,但我会很感激人们对此提出的任何建议......

1) 实例化一个具有初始容量 X 的 StringBuilder。附加 Y。当长度 < X 时,在 pos 零处插入一个零。

2) 实例化一个初始容量为 X 的 StringBuilder。当长度 < X 时,追加一个零。根据 StringBuilder 值创建一个 DecimalFormat,然后在需要时格式化数字。

3) 创建一个 Math.pow(10, X) 的新 int 并添加 Y。在新数字上使用 String.valueOf(),然后对其使用 substring(1)。

第二个显然可以分为外循环和内循环部分。

那么,有什么窍门吗?使用 10,000 次迭代的 for 循环,我从前两种方法中得到了相似的时间,而第三种方法大约快了十倍。这看起来正确吗?

下面的完整测试方法代码...

    // Setup test variables
    int numDigits = 9;
    int testNumber = 724;
    int numIterations = 10000;
    String folderHolder = null;
    DecimalFormat outputFormat = new DecimalFormat( "#,##0" );

    // StringBuilder test
    long before = System.nanoTime();
    for ( int i = 0; i < numIterations; i++ )
    {
        StringBuilder sb = new StringBuilder( numDigits );
        sb.append( testNumber );
        while ( sb.length() < numDigits )
        {
            sb.insert( 0, 0 );
        }

        folderHolder = sb.toString();
    }
    long after = System.nanoTime();
    System.out.println( "01: " + outputFormat.format( after - before ) + " nanoseconds" );
    System.out.println( "Sanity check: Folder = \"" + folderHolder + "\"" );

    // DecimalFormat test
    before = System.nanoTime();
    StringBuilder sb = new StringBuilder( numDigits );
    while ( sb.length() < numDigits )
    {
        sb.append( 0 );
    }
    DecimalFormat formatter = new DecimalFormat( sb.toString() );
    for ( int i = 0; i < numIterations; i++ )
    {
        folderHolder = formatter.format( testNumber );
    }
    after = System.nanoTime();
    System.out.println( "02: " + outputFormat.format( after - before ) + " nanoseconds" );
    System.out.println( "Sanity check: Folder = \"" + folderHolder + "\"" );

    // Substring test
    before = System.nanoTime();
    int baseNum = (int)Math.pow( 10, numDigits );
    for ( int i = 0; i < numIterations; i++ )
    {
        int newNum = baseNum + testNumber;
        folderHolder = String.valueOf( newNum ).substring( 1 );
    }
    after = System.nanoTime();
    System.out.println( "03: " + outputFormat.format( after - before ) + " nanoseconds" );
    System.out.println( "Sanity check: Folder = \"" + folderHolder + "\"" );
4

5 回答 5

8

我会停止基于微基准进行优化,而是选择看起来优雅的代码,例如String.format("%0"+numDigits+"d", testNumber)

于 2010-06-07T13:11:04.620 回答
2

使用 String.format("%0[length]d", i)

对于 8 的长度,它将是

String out = String.format("%08d", i);

它速度较慢,但​​输入和调试更复杂的代码所花费的时间可能会超过执行期间所用的总额外时间。

事实上,如果你把讨论这个问题的所有工时加起来,它很可能会大大超过节省的执行时间。

于 2010-06-07T13:11:00.830 回答
1

一个一个地插入填充字符显然很慢。如果性能确实是一个大问题,您可以使用长度为 1..n-1 的预定义字符串常量(其中n是最大预期长度),存储在相应索引处的 ArrayList 中。

如果n非常大,至少您仍然可以插入更大的块而不是单个字符。

但总的来说,正如其他人也指出的那样,优化只有在您在真实情况下分析您的应用程序并发现哪段特定代码是瓶颈时才可行。然后您可以专注于这一点(当然还要再次配置文件以验证您的更改是否确实提高了性能)。

于 2010-06-07T13:06:16.370 回答
1

这是一个与您的 StringBuilder 基本相同的解决方案,具有两个优化:

  1. 它绕过 StringBuilder 开销直接写入数组
  2. 它以相反的方式执行操作,而不是 insert(0),它每次都需要一个 arraycopy

它还假设 numDigits 将 >= 到所需的实际字符,但会正确处理负数:

before = System.nanoTime();
String arrString=null;
for ( int j = 0; j < numIterations; j++ ){
  char[] arrNum = new char[numDigits];
  int i = numDigits-1;
  boolean neg = testNumber<0;
  for(int tmp = neg?-testNumber:testNumber;tmp>0;tmp/=10){
    arrNum[i--] = (char)((tmp%10)+48);
  }
  while(i>=0){
    arrNum[i--]='0';
  }
  if(neg)arrNum[0]='-';
  arrString = new String(arrNum);
}
after = System.nanoTime();
System.out.println( "04: " + outputFormat.format( after - before ) + " nanoseconds" );
System.out.println( "Sanity check: Folder = \"" + arrString + "\"" );

这种方法在我的机器上的阴性结果优于您的样本,并且在阳性结果方面具有可比性:

01: 18,090,933 nanoseconds
Sanity check: Folder = "000000742"
02: 22,659,205 nanoseconds
Sanity check: Folder = "000000742"
03: 2,309,949 nanoseconds
Sanity check: Folder = "000000742"
04: 6,380,892 nanoseconds
Sanity check: Folder = "000000742"

01: 14,933,369 nanoseconds
Sanity check: Folder = "0000-2745"
02: 21,685,158 nanoseconds
Sanity check: Folder = "-000002745"
03: 3,213,270 nanoseconds
Sanity check: Folder = "99997255"
04: 1,255,660 nanoseconds
Sanity check: Folder = "-00002745"

编辑:我注意到您的测试在迭代循环中重新使用了一些对象,而我在我的中没有这样做(例如不重新计算子字符串版本中的 baseNum)。当我将测试更改为一致时(不重新执行任何对象/计算,我的版本比您的版本执行得更好:

01: 18,377,935 nanoseconds
Sanity check: Folder = "000000742"
02: 69,443,911 nanoseconds
Sanity check: Folder = "000000742"
03: 6,410,263 nanoseconds
Sanity check: Folder = "000000742"
04: 996,622 nanoseconds
Sanity check: Folder = "000000742"

当然,正如其他人所提到的,由于虚拟机执行的所有优化以及无法控制它们,微基准测试非常困难/“笨拙”。

于 2010-06-07T14:09:41.210 回答
0

这个可能相关的链接讨论了许多方法。我会推荐 Apache 选项 StringUtils,它可能是绝对最快的,也可能不是,但它通常是最容易理解的选项之一,并且已经使用了 )&##@,因此它可能不会中断在一些不可预见的边缘情况下。;)

于 2010-06-07T14:06:27.420 回答