将ASCII字节数组转换为字符串。

19
我试图将包含ASCII字符的byte[]传递给log4j,以使用显而易见的表示形式记录到文件中。当我仅传递byt[]时,它当然会被视为对象,日志几乎没有用处。尝试使用new String(byte[] data)将它们转换为字符串时,应用程序的性能减半。
我该如何有效地传递它们,而不会产生将它们转换为字符串的约30微秒时间惩罚?
此外,为什么转换需要这么长时间呢?
谢谢。
编辑
我应该补充说明,我在优化延迟 - 是的,30微秒确实有所影响!此外,这些数组的大小从约100个字节变化到几千个字节。
5个回答

17

ASCII是为数不多的编码之一,可以无需算术或查表就能将其转换为/从UTF16进行转换,因此可以手动转换:

String convert(byte[] data) {
    StringBuilder sb = new StringBuilder(data.length);
    for (int i = 0; i < data.length; ++ i) {
        if (data[i] < 0) throw new IllegalArgumentException();
        sb.append((char) data[i]);
    }
    return sb.toString();
}

但请确保它真的是ASCII,否则你会得到垃圾。


这段代码对我有效,但使用new String(byteArray)使得我的安卓应用程序崩溃了。你能解释一下其中的区别吗? - Denny Weinberg

14
你想做的是将byte[]数组的处理延迟到log4j决定要记录消息时。这样,你可以在测试时以DEBUG级别记录它,然后在生产环境中禁用它。例如,你可以:
final byte[] myArray = ...;
Logger.getLogger(MyClass.class).debug(new Object() {
    @Override public String toString() {
        return new String(myArray);
    }
});

现在,只有当您实际记录数据时,才会受到速度惩罚,因为只有在log4j决定实际记录消息时才调用toString方法!
现在我不确定您所说的“显然的表示”是什么意思,所以我假设您的意思是通过重新解释字节作为默认字符编码来转换为字符串。如果您正在处理二进制数据,则这显然是无用的。在这种情况下,我建议使用Arrays.toString(byte [])创建格式化字符串,如下所示:
[54, 23, 65, ...]

2
很好,使用异步记录器可以将转换移出关键路径。 - jwoolard

8
如果你的数据确实是ASCII(即7位数据),那么你应该使用new String(data, "US-ASCII")而不是依赖于默认编码。这可能比尝试将其解释为平台默认编码更快(它可以是需要更多内省的UTF-8)。
你也可以通过缓存Charset实例并调用new String(data, charset)来避免每次字符集查找的开销,从而加快速度。
话虽如此:在生产环境中我很久以前就没有看到真正的ASCII数据了。

这与finnw的答案有什么区别? - Zyoo
2
先生,这取决于您所处的生产环境。我每天都看到它。 - C4F


1

性能减半?这个字节数组有多大?如果它是1MB,那么肯定有比仅仅从字节转换为字符(虽然这应该足够快)更多的因素需要考虑。将1MB的数据写入日志文件而不是“仅仅”100字节(可能由byte[].toString()生成)显然需要一些时间。磁盘文件系统不像RAM内存那样快。

您需要更改字节数组的字符串表示形式。也许加上一些更敏感的信息,例如与之关联的名称(文件名?),其长度等等。毕竟,这个字节数组实际上代表了什么?

编辑:我记不得在你的问题中看到过“大约30微秒”的短语,也许你在提问后5分钟内进行了编辑,但这实际上是微观优化,通常不会导致“性能减半”。除非您每秒钟写入一百万次(即使是这样,为什么要这样做?难道您不是过度使用“日志记录”现象吗?)。


这些数组的大小差别很大,从大约150字节到4000字节不等。关于您最后提到的问题,我正在优化延迟而非吞吐量 - 所以我需要将此转换移出关键路径,或者加速它... - jwoolard
另外,遗憾的是,需要记录所有这些数据 - 是的,这是大量的数据... - jwoolard
那么你的瓶颈更多是在磁盘IO上,而不是在Java代码上——正如我所预料的。 - BalusC

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