Java 中文官方教程 2022 版(三十)(1)https://developer.aliyun.com/article/1487956
示例用法
本页面包含一些代码片段,展示了几种常见场景。
从代码点创建 String
String newString(int codePoint) { return new String(Character.toChars(codePoint)); }
从代码点创建 String
- 为 BMP 字符进行优化
Character.toChars
方法创建一个临时数组,仅使用一次然后丢弃。如果这对性能产生负面影响,你可以使用以下针对 BMP 字符(由单个 char
值表示的字符)进行优化的方法。在这种方法中,toChars
仅用于补充字符。
String newString(int codePoint) { if (Character.charCount(codePoint) == 1) { return String.valueOf(codePoint); } else { return new String(Character.toChars(codePoint)); } }
批量创建 String
对象
要创建大量字符串,前面代码片段的批量版本重用了 toChars
方法使用的数组。该方法为每个代码点创建一个单独的 String
实例,并针对 BMP 字符进行了优化。
String[] newStrings(int[] codePoints) { String[] result = new String[codePoints.length]; char[] codeUnits = new char[2]; for (int i = 0; i < codePoints.length; i++) { int count = Character.toChars(codePoints[i], codeUnits, 0); result[i] = new String(codeUnits, 0, count); } return result; }
生成消息
格式化 API 支持补充字符。以下示例是生成消息的简单方法。
// recommended System.out.printf("Character %c is invalid.%n", codePoint);
以下方法简单且避免了连接,这使得文本更难本地化,因为并非所有语言都按照英语的顺序将数字值插入字符串中。
// not recommended System.out.println("Character " + String.valueOf(char) + " is invalid.");
设计考虑事项
要编写能够无缝运行于任何语言和任何脚本的代码,有几点需要牢记。
考虑事项 | 原因 |
避免使用char 数据类型的方法。 |
避免使用char 原始数据类型或使用char 数据类型的方法,因为使用该数据类型的代码对补充字符不起作用。对于需要char 类型参数的方法,尽可能使用相应的int 方法。例如,使用Character.isDigit(int) 方法而不是Character.isDigit(char) 方法。 |
使用isValidCodePoint 方法验证代码点值。 |
代码点被定义为int 数据类型,允许值超出从 0x0000 到 0x10FFFF 的有效代码点值范围。出于性能原因,接受代码点值作为参数的方法不会检查参数的有效性,但您可以使用isValidCodePoint 方法检查该值。 |
使用codePointCount 方法计算字符数。 |
String.length() 方法返回字符串中代码单元或 16 位char 值的数量。如果字符串包含补充字符,则计数可能会误导,因为它不会反映真实的代码点数量。要准确计算字符数(包括补充字符),请使用codePointCount 方法。 |
使用String.toUpperCase(int codePoint) 和String.toLowerCase(int codePoint) 方法而不是Character.toUpperCase(int codePoint) 或Character.toLowerCase(int codePoint) 方法。 |
虽然Character.toUpperCase(int) 和Character.toLowerCase(int) 方法可以处理代码点值,但有些字符无法进行一对一转换。例如,德语小写字符ß在转换为大写时变为两个字符 SS。同样,希腊语小写 Sigma 字符在字符串中的位置不同而有所不同。Character.toUpperCase(int) 和Character.toLowerCase(int) 方法无法处理这些情况;然而,String.toUpperCase 和String.toLowerCase 方法可以正确处理这些情况。 |
删除字符时要小心。 | 在调用StringBuilder.deleteCharAt(int index) 或StringBuffer.deleteCharAt(int index) 方法时,索引指向补充字符时,只会删除该字符的前半部分(第一个char 值)。首先,调用Character.charCount 方法对字符进行检查,以确定必须删除一个或两个char 值。 |
在对序列中的字符进行反转时要小心。当在包含补充字符的文本上调用StringBuffer.reverse() 或StringBuilder.reverse() 方法时,高低代理对会被反转,导致不正确甚至可能无效的代理对。 |
更多信息
有关补充字符的更多信息,请参考以下资源。
检测文本边界
原文:
docs.oracle.com/javase/tutorial/i18n/text/boundaryintro.html
操纵文本的应用程序需要定位文本内的边界。例如,考虑一些文字处理器的常见功能:突出显示一个字符,剪切一个单词,将光标移动到下一个句子,以及在行尾换行一个单词。为了执行这些功能,文字处理器必须能够检测文本中的逻辑边界。幸运的是,您不必编写自己的例程来执行边界分析。相反,您可以利用BreakIterator
类提供的方法。
关于 BreakIterator 类
本节讨论了BreakIterator
类的实例化方法和虚拟光标。
字符边界
在本节中,您将了解用户字符和 Unicode 字符之间的区别,以及如何使用BreakIterator
定位用户字符。
词边界
如果您的应用程序需要在文本中选择或定位单词,使用BreakIterator
会很有帮助。
句子边界
确定句子边界可能会有问题,因为许多书面语言中句子终止符的使用是模棱两可的。本节将讨论您可能遇到的一些问题,以及BreakIterator
如何处理这些问题。
行边界
本节描述了如何使用BreakIterator
在文本字符串中定位潜在的换行符。
关于 BreakIterator 类
BreakIterator
类是区域敏感的,因为文本边界随语言而变化。例如,换行的语法规则并非所有语言都相同。要确定BreakIterator
类支持哪些区域设置,请调用getAvailableLocales
方法,如下所示:
Locale[] locales = BreakIterator.getAvailableLocales();
您可以使用BreakIterator
类分析四种边界类型:字符、单词、句子和潜在的换行符。在实例化BreakIterator
时,调用适当的工厂方法:
getCharacterInstance
getWordInstance
getSentenceInstance
getLineInstance
每个BreakIterator
实例只能检测一种类型的边界。例如,如果您想定位字符和单词边界,您需要创建两个单独的实例。
BreakIterator
具有一个想象的光标,指向文本字符串中的当前边界。您可以使用previous
和next
方法在文本中移动此光标。例如,如果您使用getWordInstance
创建了一个BreakIterator
,每次调用next
方法时,光标都会移动到文本中的下一个单词边界。光标移动方法返回一个整数,指示边界的位置。此位置是文本字符串中将跟随边界的字符的索引。与字符串索引一样,边界是从零开始的。第一个边界在 0 处,最后一个边界是字符串的长度。以下图显示了next
和previous
方法在文本行中检测到的单词边界:
*此图已经缩小以适应页面。
点击图像以查看其自然大小。*
您应该仅将BreakIterator
类与自然语言文本一起使用。要对编程语言进行标记化,请使用StreamTokenizer
类。
接下来的部分为每种边界分析类型提供示例。编码示例来自名为BreakIteratorDemo.java
的源代码文件。
字符边界
如果您的应用程序允许最终用户突出显示单个字符或逐个字符地移动光标穿过文本,则需要定位字符边界。要创建一个定位字符边界的BreakIterator
,您可以调用getCharacterInstance
方法,如下所示:
BreakIterator characterIterator = BreakIterator.getCharacterInstance(currentLocale);
这种类型的BreakIterator
检测用户字符之间的边界,而不仅仅是 Unicode 字符。
用户字符可能由多个 Unicode 字符组成。例如,用户字符ü可以由组合 Unicode 字符\u0075(u)和\u00a8(¨)组成。然而,这并不是最好的例子,因为字符ü也可以用单个 Unicode 字符\u00fc 表示。我们将借助阿拉伯语言来举一个更现实的例子。
在阿拉伯语中,房子的单词是:
这个词包含三个用户字符,但它由以下六个 Unicode 字符组成:
String house = "\u0628" + "\u064e" + "\u064a" + "\u0652" + "\u067a" + "\u064f";
字符串house
中位置 1、3 和 5 的 Unicode 字符是变音符号。阿拉伯语需要变音符号,因为它们可以改变单词的含义。示例中的变音符号是非间隔字符,因为它们出现在基本字符的上方。在阿拉伯语文字处理器中,您不能在屏幕上每个 Unicode 字符移动一次光标。相反,您必须为每个用户字符移动一次光标,这可能由多个 Unicode 字符组成。因此,您必须使用BreakIterator
来扫描字符串中的用户字符。
示例程序BreakIteratorDemo
创建一个BreakIterator
来扫描阿拉伯字符。程序将这个BreakIterator
与先前创建的String
对象一起传递给名为listPositions
的方法:
BreakIterator arCharIterator = BreakIterator.getCharacterInstance( new Locale ("ar","SA")); listPositions (house, arCharIterator);
listPositions
方法使用BreakIterator
来定位字符串中的字符边界。请注意,BreakIteratorDemo
使用setText
方法将特定字符串分配给BreakIterator
。程序使用first
方法检索第一个字符边界,然后调用next
方法,直到返回常量BreakIterator.DONE
。此例程的代码如下:
static void listPositions(String target, BreakIterator iterator) { iterator.setText(target); int boundary = iterator.first(); while (boundary != BreakIterator.DONE) { System.out.println (boundary); boundary = iterator.next(); } }
listPositions
方法打印出字符串house
中用户字符的以下边界位置。请注意,变音符号的位置(1、3、5)没有列出:
0 2 4 6
单词边界
您调用getWordIterator
方法来实例化一个检测单词边界的BreakIterator
:
BreakIterator wordIterator = BreakIterator.getWordInstance(currentLocale);
当您的应用程序需要对单词执行操作时,您会想要创建这样一个BreakIterator
。这些操作可能是常见的单词处理功能,如选择、剪切、粘贴和复制。或者,您的应用程序可能会搜索单词,并且必须能够区分整个单词和简单字符串。
当BreakIterator
分析单词边界时,它区分单词和不属于单词的字符。这些字符包括空格、制表符、标点符号和大多数符号,在两侧都有单词边界。
接下来的示例来自程序BreakIteratorDemo
,标记了一些文本中的单词边界。该程序创建了BreakIterator
,然后调用markBoundaries
方法:
Locale currentLocale = new Locale ("en","US"); BreakIterator wordIterator = BreakIterator.getWordInstance(currentLocale); String someText = "She stopped. " + "She said, \"Hello there,\" and then went " + "on."; markBoundaries(someText, wordIterator);
markBoundaries
方法在BreakIteratorDemo.java
中定义。该方法通过在目标字符串下方打印插入符号(^)来标记边界。在接下来的代码中,请注意while
循环,其中markBoundaries
通过调用next
方法扫描字符串:
static void markBoundaries(String target, BreakIterator iterator) { StringBuffer markers = new StringBuffer(); markers.setLength(target.length() + 1); for (int k = 0; k < markers.length(); k++) { markers.setCharAt(k,' '); } iterator.setText(target); int boundary = iterator.first(); while (boundary != BreakIterator.DONE) { markers.setCharAt(boundary,'^'); boundary = iterator.next(); } System.out.println(target); System.out.println(markers); }
markBoundaries
方法的输出如下。请注意插入符号(^)相对于标点符号和空格的位置:
She stopped. She said, "Hello there," and then ^ ^^ ^^ ^ ^^ ^^^^ ^^ ^^^^ ^^ ^ went on. ^ ^^ ^^
BreakIterator
类使得从文本中选择单词变得容易。您不必编写自己的处理各种语言标点规则的例程;BreakIterator
类会为您处理这些。
以下示例中的extractWords
方法提取并打印给定字符串的单词。请注意,该方法使用Character.isLetterOrDigit
来避免打印包含空格字符的“单词”。
static void extractWords(String target, BreakIterator wordIterator) { wordIterator.setText(target); int start = wordIterator.first(); int end = wordIterator.next(); while (end != BreakIterator.DONE) { String word = target.substring(start,end); if (Character.isLetterOrDigit(word.charAt(0))) { System.out.println(word); } start = end; end = wordIterator.next(); } }
BreakIteratorDemo
程序调用extractWords
,将其传递给前面示例中使用的相同目标字符串。extractWords
方法打印出以下单词列表:
She stopped She said Hello there and then went on
句子边界
你可以使用BreakIterator
来确定句子边界。首先通过getSentenceInstance
方法创建一个BreakIterator
:
BreakIterator sentenceIterator = BreakIterator.getSentenceInstance(currentLocale);
为了显示句子边界,程序使用了markBoundaries
方法,该方法在单词边界一节中有讨论。markBoundaries
方法在字符串下方打印插入符号(^)来指示边界位置。以下是一些示例:
She stopped. She said, "Hello there," and then went on. ^ ^ ^ He's vanished! What will we do? It's up to us. ^ ^ ^ ^ Please add 1.5 liters to the tank. ^
行边界
应用程序格式化文本或执行换行操作必须找到潜在的换行位置。您可以使用使用getLineInstance
方法创建的BreakIterator
来找到这些换行位置或边界:
BreakIterator lineIterator = BreakIterator.getLineInstance(currentLocale);
这个BreakIterator
确定字符串中文本可以断开以继续到下一行的位置。BreakIterator
检测到的位置是潜在的换行位置。在屏幕上显示的实际换行可能不同。
接下来的两个示例使用BreakIteratorDemo.java
的markBoundaries
方法来显示BreakIterator
检测到的行边界。markBoundaries
方法通过在目标字符串下方打印插入符号(^)来指示行边界。
根据BreakIterator
,在一系列空格字符(空格、制表符、换行符)的终止后发生行边界。在下面的示例中,请注意您可以在检测到的任何边界处断开行:
She stopped. She said, "Hello there," and then went on. ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
潜在的换行位置也会在连字符后立即发生:
There are twenty-four hours in a day. ^ ^ ^ ^ ^ ^ ^ ^ ^
下一个示例将长文本字符串分成固定长度的行,使用名为formatLines
的方法。该方法使用BreakIterator
来定位潜在的换行位置。formatLines
方法简短、简单,并且由于使用了BreakIterator
,与语言环境无关。以下是源代码:
static void formatLines( String target, int maxLength, Locale currentLocale) { BreakIterator boundary = BreakIterator. getLineInstance(currentLocale); boundary.setText(target); int start = boundary.first(); int end = boundary.next(); int lineLength = 0; while (end != BreakIterator.DONE) { String word = target.substring(start,end); lineLength = lineLength + word.length(); if (lineLength >= maxLength) { System.out.println(); lineLength = word.length(); } System.out.print(word); start = end; end = boundary.next(); } }
BreakIteratorDemo
程序调用formatLines
方法如下:
String moreText = "She said, \"Hello there,\" and then " + "went on down the street. When she stopped " + "to look at the fur coats in a shop + " "window, her dog growled. \"Sorry Jake,\" " + "she said. \"I didn't know you would take " + "it personally.\""; formatLines(moreText, 30, currentLocale);
调用formatLines
的输出为:
She said, "Hello there," and then went on down the street. When she stopped to look at the fur coats in a shop window, her dog growled. "Sorry Jake," she said. "I didn't know you would take it personally."
将拉丁数字转换为其他 Unicode 数字
原文:
docs.oracle.com/javase/tutorial/i18n/text/shapedDigits.html
默认情况下,当文本包含数字值时,这些值将使用拉丁(欧洲)数字显示。如果希望使用其他 Unicode 数字形状,请使用java.awt.font.NumericShaper
类。NumericShaper
API 使您能够以任何 Unicode 数字形状显示内部表示为 ASCII 值的数字值。
下面的代码片段来自ArabicDigits
示例,展示了如何使用NumericShaper
实例将拉丁数字转换为阿拉伯数字。确定整形操作的行已加粗。
ArabicDigitsPanel(String fontname) { HashMap map = new HashMap(); Font font = new Font(fontname, Font.PLAIN, 60); map.put(TextAttribute.FONT, font); map.put(TextAttribute.NUMERIC_SHAPING, NumericShaper.getShaper(NumericShaper.ARABIC)); FontRenderContext frc = new FontRenderContext(null, false, false); layout = new TextLayout(text, map, frc); } // ... public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; layout.draw(g2d, 10, 50); }
获取阿拉伯数字的NumericShaper
实例,并将其放入HashMap
中,用于TextLayout.NUMERIC_SHAPING
属性键。哈希映射传递给TextLayout
实例。在paint
方法中呈现文本后,数字以所需脚本显示。在此示例中,拉丁数字 0 到 9 以阿拉伯数字形式显示。
前面的示例使用NumericShaper.ARABIC
常量来检索所需的整形器,但NumericShaper
类为许多语言提供了常量。这些常量被定义为位掩码,并称为NumericShaper
基于位掩码的常量。
Java 中文官方教程 2022 版(三十)(3)https://developer.aliyun.com/article/1487965