Java中最快的char转换为字符串方法

6

我看到在Java中将char转换为String有四种选择。

v = Something.lookup(new String((char)binaryData[idx])); // SORRY! Wrong.
v = Something.lookup("" + (char)binaryData[idx]);
v = Something.lookup(String.valueOf((char)binaryData[idx]));
v = Something.lookup(Character.toString((char)binaryData[idx])));

我认为第一个选项最慢。第二个选项非常方便。我推测第三个选项可能会返回先前创建的String实例,但我不确定,API文档也没有说明。选项四也是如此。如果能够重用实例,那么基于哈希的查找就可以利用String中的hashCode()缓存,这将非常有利。(虽然这个特性未在API文档中描述,但很多人告诉我)

我来自C++,感觉缺乏复杂性信息让人困扰。:-) 我的猜测正确吗?我们是否有任何官方文档声明了性能保证和缓存机制?


6
“第二种方法非常方便”- 我认为如果你试图阅读代码的话,这并不方便。该代码使用了字符串拼接和空字符串,它们都不是实际想要实现的部分。在我看来最好完全使用String.valueOf,并且您是否已经衡量过其中任何一个呢?您知道这是否真的是代码的瓶颈吗?您是否尝试确定String.valueof是否确实缓存了值?(很容易判断……)假设“binaryData”是一个“byte[]”,那么您可以轻松地自己构建一个带有256个字符串的“String[]”,以绝对保证缓存。 - Jon Skeet
1
还有一件事需要记住:这些二进制数据是从哪里来的?你确定它们真的代表了Java中的“char”值吗?不用担心编码问题吗? - GhostCat
1
由于您不是将字符转换为字符串,而是将字节转换为字符串,并且在不考虑编码和多字节字符的情况下进行转换,因此所有这些选项都是错误的。当您的解决方案本身不正确时,速度是您最不需要担心的事情。 - biziclop
1
只需要问问自己,在过去,字符串转换有多少次拖累了应用程序的性能?又有多少次遇到了“神秘”的编码问题?这将为您提供速度和正确性的相对重要性。但是,如果您真的需要进行字符转换,除了选项1和2之外的任何选项都可以使用。使用new String()创建新字符串实例很少是一个好主意,选项2是难看的隐式转换误用。 - biziclop
1
@Notinlist 所以你非常注重高性能领域。我肯定会选择一个包含所有可能字符串的表格,以避免创建数十亿个对象。事实上,如果可能的话,我可能会尽量避免使用字符串和字符。 - biziclop
显示剩余3条评论
4个回答

9
首先,Java规范对于这四种方法的性能并没有做出任何说明,因此结果可能会因使用的JRE版本和供应商而异。
如果您使用Oracle的JRE,可以轻松地自行检查源代码!在Java8中,如下所示:
给定一个带有某个值的char c:
- new String(c)无法编译。 没有这样的构造函数。 - "" + c看起来不美观,繁琐且棘手。 内部它创建一个新的空StringBuilder并将字符附加到其中。 然后它从StringBuilder创建一个新的String实例。 - Character.toString(c)委托给String.valueOf(c)。 - String.valueOf(c)创建一个新的String实例。
那么该使用哪个呢? 最易读的方法! 在我看来,这是String.valueOf(c)或Character.toString(c)!

1
你可能想要讨论字符串内部化,以使答案更完整。我打赌所有小于 256 的 ASCII 字符在启动时都会被内部化(缓存)。 - Adam Gent
1
"" + c在HotSpot JVM中进行了特殊优化,因此速度稍快,但出于可读性的考虑,我更喜欢使用Character.toStringString.valueOf - apangin
@AdamGent 不,令人惊讶的是String.valueOf(char)根本不使用任何缓存。它只是创建一个新的String实例,将给定字符作为单元素字符数组传递。String#intern()与将字符转换为字符串无关,因此我认为这个主题并不真正适合我的回答。 - isnot2bad
我现在会使用 String.valueOf(c)。以后,我将按照Jon Skeet的建议,基于一个数组实现我的自己的byte=>String映射。 - Notinlist
当然,如果String.valueOf做到这一点,那就太糟糕了。我的意思是,如果他有可预测的字符集,比如只有前256个字符,他可以在这些字符上调用String.intern。这将节省内存和GC,如果他将单个字母字符串存储在一堆对象中...即使有一百万个“a”字母的副本,也只有一个,但对于这样小的字符串来说,可能不适合使用intern。这不是为了节省创建时间,而是为了节省内存。 - Adam Gent
@AdamGent 他可以轻松地通过使用String[]缓存来达到相同的效果,无需内部化。但即使没有某种飞行权模式,自从Java 8以来,由于一种称为“字符串去重”的技术,内存也得到了节省:https://dev59.com/8F4c5IYBdhLWcg3wapvz - isnot2bad

2
第二种方法理论上肯定比较慢,因为它需要翻译成其他语言。
v = Something.lookup(new StringBuilder().append("").append((char)binaryData[idx]).toString());

StringBuilder是使用一个初始化为16个值的char[]实现的。因此,StringBuilder选项会初始化一个大小为16的char[],只复制设置的单元格(在这种情况下只有第一个单元格)到结果字符串中。

String.valueOf(相当于Character.toString)使用大小为1的char[],然后直接设置String的支持char[],从而避免了需要复制的过程。

第一种方法不会编译(至少在java 7下不会),因为没有接受单个字符输入的String构造函数:http://docs.oracle.com/javase/7/docs/api/java/lang/String.html


我的意思是第二个选项做更多的工作,理论上应该更慢。 - EvenLisle
“理论上,理论和实践没有区别。但在实践中,有区别。” :-) 在使用Hotspot JVM 8时,这三个选项之间没有明显的区别。 - assylias
1
HotSpot JIT编译器能够识别典型的字符串连接模式:new StringBuilder().append().append()...toString(),并且可以对其进行良好的优化。实际上,"" + cCharacter.toString 更快。 - apangin
@assylles 早期版本中确实存在这个问题。但主要问题是,无论如何都不太可能有影响。因为首先解决方案本身就是错误的。 - biziclop

1
第一个解决方案无法编译。 第二个解决方案在内部创建一个字符串,调用类似于Character.valueOf(char)的代码。 第三个解决方案比第四个好,因为Character.toString(char ch)的内部实现是调用了String.valueOf
public static String toString(char c) {
    return String.valueOf(c);
}

第三个String.valueOf(char ch)的内部实现如下:

public static String valueOf(char c) {
    char data[] = {c};
    return new String(0, 1, data);
}

1
我来自C++,感觉缺乏复杂度信息有点困扰。 :-) 我的猜测是正确的吗?我们是否有任何官方文档声明性能保证和缓存机制?
回答这个问题的部分:一般情况下没有。您会找到内置集合的渐近性能信息,可能还有其他一些领域的信息,但总体而言,这些问题留给了VM实现自行决定。当然,您可以查看源代码,但请记住,有些影响性能的因素是您无法直接控制的:JIT编译和垃圾收集是最大的两个因素。
您应该因此而困扰吗?我认为不应该,Java的前提是低级性能很少是应用程序开发人员需要关注的问题。这是一个权衡,您可以争论它是否是一个好的权衡,但它就是它。
但是,当您达到可以开发真正高性能系统的程度时,您将逐步掌握所有必要的信息。

如果可以的话,我会增加更多的点赞,因为这是唯一处理问题第二部分的答案。 - Notinlist
1
@Notinlist 只是一个小补充:你的问题(或者说,你得到的答案)展示了这个政策在实践中如何给出非常少的实现保证。 ""+c 最初是一种“坏习惯”,非常不受欢迎,而且速度很慢。但是从Java 8开始,JIT编译器显然认可了这种模式,并为其生成与所有其他解决方案一样快的代码。如果有更多的实现细节保证,也许这是不可能的。 - biziclop

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