Java - 检查String大小的最快方法

11

我有以下代码在一个循环语句中。
在循环中,将字符串追加到sb(StringBuilder)中并检查sb的大小是否达到5MB。

if (sb.toString().getBytes("UTF-8").length >= 5242880) {
    // Do something
}

这个方法可以工作,但是它非常慢(在检查大小方面)
有什么更快的方法可以实现这个功能吗?

3个回答

15

您可以使用以下方法快速计算UTF-8长度:

public static int utf8Length(CharSequence cs) {
    return cs.codePoints()
        .map(cp -> cp<=0x7ff? cp<=0x7f? 1: 2: cp<=0xffff? 3: 4)
        .sum();
}
如果内容主要由ASCII字符组成,使用以下方法可能会稍微快一些:
public static int utf8Length(CharSequence cs) {
    return cs.length()
         + cs.codePoints().filter(cp -> cp>0x7f).map(cp -> cp<=0x7ff? 1: 2).sum();
}

但您也可以考虑优化潜力,而不是重新计算整个大小,而只计算您附加到 StringBuilder 的新片段的大小,类似于

instead.

    StringBuilder sb = new StringBuilder();
    int length = 0;
    for(…; …; …) {
        String s = … //calculateNextString();
        sb.append(s);
        length += utf8Length(s);
        if(length >= 5242880) {
            // Do something

            // in case you're flushing the data:
            sb.setLength(0);
            length = 0;
        }
    }

假设您正在追加包含代理对的片段,那么它们始终是完整的,而不是分成两半。对于普通应用程序来说,这应该总是成立的。

另一个可能性是由Didier-L提出的,即将计算延迟到 StringBuilder 达到阈值除以三的长度之前,因为在此之前,不可能具有大于阈值的 UTF-8 长度。然而,只有在某些情况下未达到 threshold / 3 时才会有益。


4
为了进一步优化,考虑到一个字符最多占用3个字节,你可以在StringBuilder长度达到5MB/3之前避免计算长度。 - Didier L
@Holger,在jdk-9中将会有String::codePoints,这将使ASCII和非ASCII字符串之间有所区别... 另外,这种技术仅适用于UTF-8,但仍然很不错。 - Eugene
1
@Eugene:计算UTF-8长度是这个练习的唯一目的。此外,Java 9对于codePoints()的实现不会影响这个答案。这个答案的两种解决方案之间的区别在于第二种只针对ASCII字符执行一个条件并跳过加法操作。修正错误后,这两个变体在最坏情况下不再有区别,因此第二个总是胜出。一个便宜的“isAllASCII”方法会很有帮助,但据我所知,Java 9只会在内部区分iso-latin-1和其他字符串。 - Holger
1
@DidierL:“一个字符最多占用3个字节” - 一个单独的char,是的,但Java字符串使用UTF-16表示Unicode代码点,因此字符串中每个代码点可能有1或2个char。在标准的UTF-8中,代码点可以编码高达4个字节,其中编码为3个字节的代码点仅需要1个Java char,但编码为4个字节的代码点需要2个一起运作的Java char - Remy Lebeau
1
@RemyLebeau,这并不改变我的推理,因为String/StringBuilderlength()char数量,所以如果一个代码点占用2个char,它将被计算为最多6个字节,这仍然是高估的,因此与此优化兼容。 - Didier L
@Remy Lebeau:除了Didier L的解释之外,您还可以查看我的第二个utf8Length变体,它已经考虑了代理字符与UTF-8表示之间的关系。结果是字符串长度,即每个字符串的char数,加上每个代码点最多2个,因此每个char不可能超过3个。对于BMP之外的字符,它将计算两个char加上代码点的2个,导致4,这是代码点的UTF-8字节数,但实际上每个char只有2个字节。 - Holger

9
如果您循环1000次,将生成1000个字符串,然后将其转换为“UTF-8字节”数组,以获取长度。
我建议通过存储第一个长度来减少转换。然后,在每次循环中,仅获取添加值的长度,这样就可以进行加法操作了。
int length = sb.toString().getBytes("UTF-8").length;
for(String s : list){
    sb.append(s);
    length += s.getBytes("UTF-8").length;
    if(...){
    ...
    }
}

这将减少内存使用和转换成本。

2
考虑使用 ByteArrayOutputStream 和 OutputStreamWriter 代替 StringBuilder。使用 ByteArrayOutputStream.size() 来测试大小。

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