如何在Java中正确计算字符串的长度?

22

我知道Java中有String#lengthCharacter中的各种方法,它们更多或少地处理码元/码点。

在Java中,按照Unicode标准(UAX#29)返回结果,考虑语言/区域设置、规范化和字形群集等因素,建议采用什么方式?

5个回答

33

Java字符串长度的正常模型

String.length()被规定为返回 String 中 char 值("code units")的数量。这是 Java 字符串长度最通用的定义,详见下文。

基于后端数组/数组片段大小来计算 length 语义的描述1是不正确的。length() 返回的值也是后端数组或数组片段的大小仅仅是典型 Java 类库的实现细节。String 没有必要这样实现。事实上,我认为我看到过一些不是这样实现的 Java String 实现。


字符串长度的另外几种模型

要获取字符串中 Unicode 代码点(codepoints) 的数量,请使用 str.codePointCount(0, str.length()) -- 参见javadoc

要获取特定编码(charset)下的字符串大小(以字节为单位),请使用 str.getBytes(charset).length2

要处理与语言环境相关的问题,您可以使用Normalizer将字符串规范化为最适合您用例的形式,然后像上面那样使用codePointCount。但在某些情况下,即使这样也不起作用;例如,匈牙利语字母计数规则,Unicode 标准显然没有考虑到。


通常情况下可以使用 String.length()

大多数应用程序使用 String.length() 的原因是它们不关心以人为中心的方式计算单词、文本等中的字符数。例如,如果我执行此操作:

String s = "hi mum how are you";
int pos = s.indexOf("mum");
String textAfterMum = s.substring(pos + "mum".length());

重要的是,"mum".length() 不返回代码点,也不是语言上正确的字符计数。它使用适合当前任务的模型来测量字符串的长度,并能正常工作。

当涉及到多语言文本分析时,情况会变得更加复杂;例如搜索单词。但即使这样,在开始之前对文本和参数进行规范化后,大部分时间你仍然可以安全地使用“代码单元”而不是“代码点”进行编码,也就是说,length() 仍然有效。


1- 此描述在某些版本的问题中出现。如果您有足够的声望点,请查看编辑历史记录。
2- 使用 str.getBytes(charset).length 就意味着进行编码并将其丢弃。可能有一种通用方法可以在不进行此副本的情况下完成此操作。这将涉及将 String 包装为 CharBuffer,创建一个没有备份以充当字节计数器的自定义 ByteBuffer,然后使用 Encoder.encode(...) 来计算字节数。注意:我尚未尝试过这个方法,除非您有明确的证据表明 getBytes(charset) 是显著的性能瓶颈,否则不建议尝试。


17

java.text.BreakIterator 能够迭代文本并且能够报告“字符”,单词,句子和行边界。

考虑以下代码:

def length(text: String, locale: java.util.Locale = java.util.Locale.ENGLISH) = {
  val charIterator = java.text.BreakIterator.getCharacterInstance(locale)
  charIterator.setText(text)

  var result = 0
  while(charIterator.next() != BreakIterator.DONE) result += 1
  result
}

运行它:

scala> val text = "Thîs lóo̰ks we̐ird!"
text: java.lang.String = Thîs lóo̰ks we̐ird!

scala> val length = length(text)
length: Int = 17

scala> val codepoints = text.codePointCount(0, text.length)
codepoints: Int = 21 

使用代理对:

scala> val parens = "\uDBFF\uDFFCsurpi\u0301se!\uDBFF\uDFFD"
parens: java.lang.String = surpíse!

scala> val length = length(parens)
length: Int = 10

scala> val codepoints = parens.codePointCount(0, parens.length)
codepoints: Int = 11

scala> val codeunits = parens.length
codeunits: Int = 13

在大多数情况下,这应该可以完成任务。


12

这取决于你所说的“字符串长度”的确切含义:

  • String.length() 返回 Stringchars 的数量。这通常只对编程相关的任务有用,例如分配缓冲区,因为多字节编码可能会导致问题,这意味着一个char并不意味着一个Unicode code point
  • String.codePointCount(int, int)Character.codePointCount(CharSequence,int,int) 都返回String中Unicode代码点的数量。这通常只对需要将String视为一系列Unicode代码点而无需担心多字节编码干扰的编程相关任务有用。
  • BreakIterator.getCharacterInstance(Locale) 可用于获取给定LocaleString中下一个grapheme。多次使用此方法可以让您计算String中图形符号的数量。由于在大多数情况下图形符号基本上是字母,因此此方法对于获取String包含的可写字符数非常有用。本质上,此方法返回的数字与手动计算String中字母数的数字大致相同,因此它非常适用于调整用户界面的大小和拆分Strings而不会破坏数据。
为了让您了解每种不同方法如何返回完全相同数据的不同长度,我创建了this class来快速生成包含在this page中的Unicode文本长度,该页面旨在提供对具有非英语字符的许多不同语言的全面测试。以下是在三种不同方式(无规范化,NFCNFD)下对输入文件进行规范化后执行该代码的结果:
Input UTF-8 String
>>  String.length() = 3431
>>  String.codePointCount(int,int) = 3431
>>  BreakIterator.getCharacterInstance(Locale) = 3386
NFC Normalized UTF-8 String
>>  String.length() = 3431
>>  String.codePointCount(int,int) = 3431
>>  BreakIterator.getCharacterInstance(Locale) = 3386
NFD Normalized UTF-8 String
>>  String.length() = 3554
>>  String.codePointCount(int,int) = 3554
>>  BreakIterator.getCharacterInstance(Locale) = 3386

正如您所看到的,即使是“看起来相同”的String如果使用String.length()String.codePointCount(int,int)可能会给出不同的长度结果。

关于此主题以及其他类似主题的更多信息,您应该阅读此博客文章,其中涵盖了有关使用Java正确处理Unicode的各种基础知识。


一种整洁的方法仍然很好用(在JDK 20上),尽管链接页面的计数有些变化。您为什么排除了NFKC和NFKD的测试?我调整了您的代码以包括它们。有趣的是,对于归一化,它们两者都比您的三种方法具有一个更大的BreakIterator计数,尽管我还没有弄清楚原因。 - skomisa

0

如果你是指按照语言的语法规则计算字符串的长度,那么答案是否定的,Java 或者其他任何地方都没有这样的算法。

除非算法还进行了完整的语义分析。

例如,在匈牙利语中,szzs 可以被视为一个字母或两个字母,这取决于它们出现在单词中的组合。(例如:ország 是 5 个字母,而 torzság 是 7 个字母。)

更新:如果你只想要 Unicode 标准字符数(就像我指出的那样,不太准确),将字符串转换为 java.text.NormalizerNFKC 表单可能是一种解决方案。


-1

.indexOf()方法提供了一个提示:

int length = (yourString + "whatever").indexOf("whatever");

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