在Java中将数字填充到一定位数的最快方法
-
24-10-2019 - |
题
我正在尝试创建一段经过良好优化的代码,以根据数据库生成的序列号 (Y) 创建长度为 X 位数的数字(其中 X 是从运行时属性文件中读取的),然后将其用于文件夹-保存文件时的名称。
到目前为止,我已经提出了三个想法,其中最快的是最后一个,但我很感激人们对此提出的任何建议......
1)实例化一个StringBuilder,初始容量为X。追加 Y。当长度 < X 时,在零位置插入一个零。
2)实例化一个StringBuilder,初始容量为X。当长度 < 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 + "\"" );
解决方案
我会停止基于微基准进行优化,并选择看起来优雅的代号的东西,例如 String.format("%0"+numDigits+"d", testNumber)
其他提示
使用 String.format("%0[length]d", i)
对于长度为 8 的情况,它是
String out = String.format("%08d", i);
它速度较慢,但键入和调试更复杂的代码所花费的时间可能会超过执行期间使用的总额外时间。
事实上,如果将讨论此问题所花费的所有工时加起来,很可能会大大超过节省的执行时间。
插入填充字符一个一个显然很慢。如果性能确实是一个很大的问题,您可以使用Lengts 1..n-1的预定义的字符串常数(在哪里 n 是最大的预期长度),存储在相应索引上的阵列列表中。
如果 n 很大,至少您仍然可以插入更大的块而不是单个炭。
但是总的来说,正如其他人也指出的那样,只有在您在真实情况下对应用程序进行了介绍并发现哪个特定代码是瓶颈,才有优化。然后,您可以集中精力(当然,还可以再次介绍您的变化实际上可以改善性能)。
这是一个与StringBuilder基本相同的解决方案,具有两个优化:
- 它直接写入绕过字符串构造器开销的数组
- 它以相反的方式进行操作,而不是插入(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"
当然,正如其他人所提到的那样,微观基准非常困难 /“伪造”,而VM进行的所有优化以及无法控制它们。
这可能有关 Link讨论了许多方法。我会推荐Apache选项,Stringutils,它可能是绝对最快的,但通常是最容易理解的一种,并且已将)&##@ the upped击中,所以它可能不会破裂在某些不可预见的边缘情况下。 )