【发布时间】:2011-10-13 06:33:15
【问题描述】:
我知道String#length 和Character 中的各种方法或多或少适用于代码单元/代码点。
考虑到语言/区域设置、规范化和字素集群等因素,在 Java 中实际返回 Unicode 标准 (UAX#29) 指定的结果的建议方法是什么?
【问题讨论】:
标签: java string unicode character-encoding standards
我知道String#length 和Character 中的各种方法或多或少适用于代码单元/代码点。
考虑到语言/区域设置、规范化和字素集群等因素,在 Java 中实际返回 Unicode 标准 (UAX#29) 指定的结果的建议方法是什么?
【问题讨论】:
标签: java string unicode character-encoding standards
String.length() 被指定为返回字符串中char 值(“代码单元”)的数量。这是对 Java 字符串长度的最常用的定义;见下文。
您对length 基于后备数组/数组切片大小的语义的描述1不正确。 length() 返回的值也是支持数组或数组切片的大小这一事实仅仅是典型Java类库的实现细节。 String 不需要以这种方式实现。确实,我认为我已经看到了 Java String 实现,但它并没有以这种方式实现。
要获取字符串中 Unicode 代码点的数量,请使用 str.codePointCount(0, str.length()) -- 请参阅 the javadoc。
要以特定编码(即字符集)获取字符串的大小(以字节为单位),请使用str.getBytes(charset).length2。
要处理特定于语言环境的问题,您可以使用Normalizer 将字符串规范化为最适合您的用例的任何形式,然后如上所述使用codePointCount。但在某些情况下,即使这样也行不通;例如Unicode 标准显然不满足的匈牙利字母计数规则。
大多数应用程序使用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) 是一个重要的性能瓶颈。
【讨论】:
如果你的意思是,根据语言的语法规则计算字符串的长度,那么答案是否定的,Java 中没有这样的算法,其他任何地方也没有。
除非算法还对文本进行完整的语义分析,否则不会。
例如在匈牙利语中,sz 和 zs 可以算作一两个字母,这取决于它们出现的单词的组成。(例如:ország 是 5 个字母,而 torzság 是 7 个字母.)
Uodate:如果您想要的只是 Unicode 标准字符数(正如我指出的那样,它不准确),那么使用 java.text.Normalizer 将您的字符串转换为 NFKC 形式可以成为一个解决方案。
【讨论】:
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
这在大多数情况下应该可以完成。
【讨论】:
这完全取决于您所说的“[the] 字符串的长度”:
String.length() 返回chars 在String 中的数量。这通常仅对编程相关任务(例如分配缓冲区)有用,因为多字节编码会导致问题,这意味着一个char 并不意味着一个Unicode code point。String.codePointCount(int, int) 和 Character.codePointCount(CharSequence,int,int) 都返回 String 中的 Unicode 代码点数。这通常仅适用于需要将String 视为一系列 Unicode 代码点而无需担心多字节编码干扰的编程相关任务。Locale,BreakIterator.getCharacterInstance(Locale) 可用于在String 中获取下一个grapheme。多次使用它可以让您计算String 中的字形数量。由于字形基本上 是字母(在大多数情况下),此方法对于获取String 包含的可写字符数很有用。本质上,如果您手动计算 String 中的字母数量,此方法返回的数字大致相同,这对于调整用户界面大小和拆分 Strings 等事情非常有用,而不会损坏数据。为了让您了解每种不同的方法如何为完全相同的数据返回不同的长度,我创建了this class 来快速生成this page 中包含的Unicode 文本的长度,它旨在提供对许多不同语言的非英语字符的综合测试。以下是在以三种不同方式(不规范化,NFC,NFD)规范化输入文件后执行该代码的结果:
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.length() 或String.codePointCount(int,int),即使是“外观相同”的String 也会给出不同的长度结果。
有关此主题和其他类似主题的更多信息,您应该阅读this blog post,其中涵盖了有关使用 Java 正确处理 Unicode 的各种基础知识。
【讨论】:
.indexOf() 方法给出提示:
int length = (yourString + "whatever").indexOf("whatever");
【讨论】: