Java中将整数转换为字符串的最快方法

6
每当我需要将 int 转换为 String 时,我通常会选择使用 ""+aInteger.toString(a)。现在我很好奇哪种方式更快,所以我编写了一个简单的基准测试,调用 function_1、function_2 和 function_3 各 10000000 次,并打印处理这些函数所需的时间。以下是这些函数:
public static String i="";
public static String j="";
public static String k="";

public static void function_1()
{
    i=Integer.toString(getOne());
}

public static void function_2()
{
    j=""+1;
}

public static void function_3()
{
    j=""+getOne();
}

public static int getOne()
{
    return 1;
}

输出结果为:
Benchmarking starting...
Executing function_1 10000000 time(s)...
Done executing function_1 in 476 ms.
Executing function_2 10000000 time(s)...
Done executing function_2 in 8 ms.
Executing function_3 10000000 time(s)...
Done executing function_3 in 634 ms.
Benchmarking complete!

我认为function_2之所以如此快,是因为它被编译为

public static void function_2()
{
    j="1";
}

为了避免这种情况,我使用了函数getOne()。但是有趣的部分在于:必须编译function_3而不使用Object的原始toString方法(在这种情况下为Integer.toString(1),因为int是原始类型)。我的问题是:编译器如何处理""+1,使其比调用Integer.toString(1)慢?

12
你考虑过查看字节码吗?此外,一般来说微基准测试是无用的。事情也可能发生在JIT层面上。 - Dave Newton
1
如果你查看字节码,你可能会发现function_3使用了StringBuilderfunction_1没有。在内部,StringBuilder将调用String.valueOf() - parsifal
2
@D180 - 根据你的数据,function_3function_1 。如果你想知道为什么它会“更快”,那就不要再纠结了 :-) - parsifal
1
@D180 - javap -c MyClass - parsifal
1
将整数转换为字符串的最快方法可能是使用Integer.toString(int)。(尽管聪明的程序员可能会做得更好。) - Hot Licks
显示剩余5条评论
3个回答

5

""1在编译时已知。这就是为什么在function_2中,"" + 1在转换为字节码时会被真正替换为"1"

getOne()的结果在编译时是未知的,因此连接操作将在运行时完成。但是,由于连接操作(+)效率不高,编译器很可能会将其改为基于StringBuilder.append()的实现。

不相信我?试试:javap -c ClassName.class,你会看到类似于这样的东西:

public static void function_2();
Code:
   0: ldc           #39                 // String 1
   2: putstatic     #16                 // Field j:Ljava/lang/String;
   5: return        


public static void function_3();
Code:
   0: new           #42                 // class java/lang/StringBuilder
   3: dup           
   4: invokespecial #44                 // Method java/lang/StringBuilder."<init>":()V
   7: invokestatic  #28                 // Method getOne:()I
  10: invokevirtual #45                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  13: invokevirtual #49                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  16: putstatic     #16                 // Field j:Ljava/lang/String;
  19: return 
function_2()只有一个字符串"1",而function_3()则包含了所有这些方法调用,并内部使用了StringBuilder对象。

需要记住的是,一些优化可能会在运行时发生,但这种行为取决于JVM及其配置。


1

我在1000万次迭代中测试了以下函数:

public static void no_func_maybe_constant()
{
    j= "" + 1;
}

public static void no_func_no_constant()
{
    j = "";
    j = j + 1;
}

public static void yes_func_maybe_constant()
{
    j = "" + getOne();
}

public static void yes_func_no_constant()
{
    j = "";
    j = j + getOne();
}

我的结果:

no_func_maybe_constant Took 0.028058674s
no_func_no_constant Took 1.449465242s
yes_func_maybe_constant Took 1.275561897s
yes_func_no_constant Took 1.263362257s

在不调用函数和调用函数之间的区别确实微不足道,因此在"" + 1的情况下,它确实进行了一些编译时常量计算。有趣的是,在没有函数的情况下,有时需要更少的时间...

调用函数需要这么长时间? - D180
@Tushar:也许会,也许不会。或许可以尝试使用j = ""; j = j + 1;来进行额外的测试,以尝试欺骗编译器不进行编译时常量计算,如果这是它正在做的事情的话。正如Dave在评论中所说,唯一的检查方法是查看字节码。 - Claudiu
@Tushar:如果是内联的话,唯一的区别就在于是否在编译时进行转换。如果不是内联,则区别在于是否进行函数调用。 - Claudiu
我刚刚测试了两个函数一百万次,第一个函数为空,第二个调用另一个空函数。差异很小,所以显然不是函数调用花费了那么多时间。 - D180
3
编译器可以将空函数优化为无操作。在动态编译环境下设置微基准测试非常复杂,很难正确猜测编译器实际在底层执行的操作。 - Andrew Bissell
显示剩余4条评论

0
2和3之间的差异可能是由于需要调用方法来获取整数。当您调用方法时,它会创建一个新的激活记录,这会使调用堆栈变得更加复杂,因此除非JVM的JIT能够将静态函数调用内联到单个返回值(几乎肯定不会发生在这里),否则会有更多的操作。

我希望JITC能够内联该调用。很可能由于某些原因未激活JITC(这使得整个练习都是虚假的,因为解释器开销淹没了其他所有内容)。 - Hot Licks

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