代码之家  ›  专栏  ›  技术社区  ›  Martin

将Java中的数字填充到一定位数的最快方法

  •  2
  • Martin  · 技术社区  · 14 年前

    我正在尝试创建一个经过优化的代码位,以根据数据库生成的序列号(Y)创建长度为x位数的代码(其中x是从运行时属性文件中读取的),然后在保存文件时使用文件夹名。

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

    1)用初始容量x实例化一个StringBuilder。附加y。当长度为<x时,在位置零处插入一个零。

    2)用初始容量x实例化一个StringBuilder。当长度为<x时,附加一个零。根据StringBuilder值创建decimalFormat,然后根据需要设置数字的格式。

    3)创建一个新的math.pow整数(10,x),并在新数字上添加y。使用string.valueof(),然后将其子字符串(1)。

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

    那么,有什么建议吗?使用10000个迭代的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 + "\"" );
    
    5 回复  |  直到 6 年前
        1
  •  7
  •   aioobe    14 年前

    我将停止基于微基准进行优化,转而寻求一些看起来很优雅的代码方面的东西,比如 String.format("%0"+numDigits+"d", testNumber)

        2
  •  2
  •   Andrew Spencer    6 年前

    使用string.format(“%0[长度]d”,i)

    如果是8码的话

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

    虽然速度较慢,但键入和调试更复杂的代码所花费的时间可能会超过执行过程中使用的总额外时间。

    事实上,如果将已经花费在讨论这一点上的所有工时加起来,很可能会大大超过执行时间节省。

        3
  •  1
  •   Péter Török    14 年前

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

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

    但总的来说,正如其他人指出的,只有在您分析了实际情况下的应用程序,并发现哪一段特定的代码是瓶颈时,优化才是可行的。然后,您可以关注这一点(当然,再次进行概要分析以验证您的更改实际上提高了性能)。

        4
  •  1
  •   M. Jessup    14 年前

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

    1. 它直接写入数组 绕过StringBuilder开销
    2. 它反向操作 而不是插入(0),这需要 每次一份数组副本

    它还假设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"
    

    当然,正如其他人所提到的,微基准测试是非常困难的/“软弱无力”,因为所有的优化都是由虚拟机执行的,并且无法控制它们。

        5
  •  0
  •   Community Michael Schmitz    7 年前

    This probably related 链接讨论了许多实现它的方法。我建议使用Apache选项,StringUtils,它可能是最快的,也可能不是最快的,但它通常是最容易理解的选项之一,而且它已经有了)&@的重击,因此它可能不会在某些不可预见的边缘情况下崩溃。;)