我有以下代码在一个循环语句中。
在循环中,将字符串追加到sb(StringBuilder)中并检查sb的大小是否达到5MB。
if (sb.toString().getBytes("UTF-8").length >= 5242880) {
// Do something
}
这个方法可以工作,但是它非常慢(在检查大小方面)
有什么更快的方法可以实现这个功能吗?
您可以使用以下方法快速计算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
时才会有益。
int length = sb.toString().getBytes("UTF-8").length;
for(String s : list){
sb.append(s);
length += s.getBytes("UTF-8").length;
if(...){
...
}
}
StringBuilder
长度达到5MB/3之前避免计算长度。 - Didier LString::codePoints
,这将使ASCII和非ASCII字符串之间有所区别... 另外,这种技术仅适用于UTF-8,但仍然很不错。 - EugeneUTF-8
长度是这个练习的唯一目的。此外,Java 9对于codePoints()
的实现不会影响这个答案。这个答案的两种解决方案之间的区别在于第二种只针对ASCII字符执行一个条件并跳过加法操作。修正错误后,这两个变体在最坏情况下不再有区别,因此第二个总是胜出。一个便宜的“isAllASCII”方法会很有帮助,但据我所知,Java 9只会在内部区分iso-latin-1和其他字符串。 - Holgerchar
,是的,但Java字符串使用UTF-16表示Unicode代码点,因此字符串中每个代码点可能有1或2个char
。在标准的UTF-8中,代码点可以编码高达4个字节,其中编码为3个字节的代码点仅需要1个Javachar
,但编码为4个字节的代码点需要2个一起运作的Javachar
。 - Remy LebeauString
/StringBuilder
的length()
是char
数量,所以如果一个代码点占用2个char
,它将被计算为最多6个字节,这仍然是高估的,因此与此优化兼容。 - Didier Lutf8Length
变体,它已经考虑了代理字符与UTF-8
表示之间的关系。结果是字符串长度,即每个字符串的char
数,加上每个代码点最多2
个,因此每个char
不可能超过3
个。对于BMP之外的字符,它将计算两个char
加上代码点的2
个,导致4
,这是代码点的UTF-8
字节数,但实际上每个char
只有2
个字节。 - Holger