Java 中文官方教程 2022 版(三十)(2)https://developer.aliyun.com/article/1487960
基于枚举的范围常量
指定特定数字集的另一种方法是使用NumericShaper.Range
枚举类型(enum)。这个枚举在 Java SE 7 版本中引入,还提供了一组常量。尽管这些常量是使用不同的机制定义的,但NumericShaper.ARABIC
位掩码在功能上等同于NumericShaper.Range.ARABIC
枚举,并且每种常量类型都有相应的getShaper
方法:
ArabicDigitsEnum
示例与 ArabicDigits 示例相同,只是使用NumericShaper.Range
枚举来指定语言脚本:
ArabicDigitsEnumPanel(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.Range.ARABIC)); FontRenderContext frc = new FontRenderContext(null, false, false); layout = new TextLayout(text, map, frc); }
两个getShaper
方法都接受singleRange
参数。使用任一常量类型,你都可以指定一组特定于脚本的数字范围。基于位掩码的常量可以使用OR
操作符组合,或者你可以创建一组NumericShaper.Range
枚举。以下显示了如何使用每种常量类型定义范围:
NumericShaper.MONGOLIAN | NumericShaper.THAI | NumericShaper.TIBETAN EnumSet.of( NumericShaper.Range.MONGOLIAN, NumericShaper.Range.THAI, NumericShaper.Range.TIBETAN)
你可以查询NumericShaper
对象,以确定它支持哪些范围,使用基于位掩码的整形器的getRanges
方法或基于枚举的整形器的getRangeSet
方法。
注意:
你可以使用传统的基于位掩码的常量或Range
枚举常量。在决定使用哪种时,有一些考虑因素:
Range
API 需要 JDK 7 或更高版本。Range
API 覆盖的 Unicode 范围比位掩码 API 多。- 位掩码 API 比
Range
API 快一点。
根据语言环境渲染数字
ArabicDigits
示例旨在使用特定语言的整形器,但有时必须根据语言环境渲染数字。例如,如果数字之前的文本使用泰语书写,则优先使用泰语数字。如果文本显示为藏文,则优先使用藏文数字。
你可以使用其中一个getContextualShaper
方法来实现这一点。
- getContextualShaper(int ranges)
- getContextualShaper(int ranges, int defaultContext)
- getContextualShaper(Set ranges)
- getContextualShaper(Set ranges, NumericShaper.Range defaultContext)
前两种方法使用位掩码常量,后两种使用枚举常量。接受defaultContext
参数的方法允许你指定在数字值显示在文本之前时使用的初始整形器。当没有定义默认上下文时,任何前导数字都将使用拉丁形状显示。
ShapedDigits
示例展示了整形器的工作原理。显示了五种文本布局:
- 第一个布局不使用整形器;所有数字都显示为拉丁文。
- 第二个布局将所有数字形状为阿拉伯数字,不考虑语言环境。
- 第三个布局使用了一个使用阿拉伯数字的上下文整形器。默认上下文被定义为阿拉伯文。
- 第四个布局使用了一个使用阿拉伯数字的上下文形状器,但该形状器没有指定默认上下文。
- 第五个布局使用了一个使用
ALL_RANGES
位掩码的上下文形状器,但该形状器没有指定默认上下文。
以下代码行展示了如果使用形状器,则如何定义:
- 没有使用形状器。
NumericShaper arabic = NumericShaper.getShaper(NumericShaper.ARABIC);
NumericShaper contextualArabic = NumericShaper.getContextualShaper(NumericShaper.ARABIC, NumericShaper.ARABIC);
NumericShaper contextualArabicASCII = NumericShaper.getContextualShaper(NumericShaper.ARABIC);
NumericShaper contextualAll = NumericShaper.getContextualShaper(NumericShaper.ALL_RANGES);
查看ShapedDigits.java
示例以获取更多实现细节。
转换非 Unicode 文本
原文:
docs.oracle.com/javase/tutorial/i18n/text/convertintro.html
在 Java 编程语言中,char
值表示 Unicode 字符。Unicode 是一种支持世界主要语言的 16 位字符编码。您可以在Unicode 联盟网站了解有关 Unicode 标准的更多信息。
目前很少有文本编辑器支持 Unicode 文本输入。我们用来编写本节代码示例的文本编辑器仅支持 ASCII 字符,这些字符仅限于 7 位。为了表示 ASCII 无法表示的 Unicode 字符,例如ö,我们使用\uXXXX
转义序列。转义序列中的每个X
都是一个十六进制数字。以下示例显示了如何使用转义序列表示ö字符:
String str = "\u00F6"; char c = '\u00F6'; Character letter = new Character('\u00F6');
世界各地的系统使用各种字符编码。目前,很少有这些编码符合 Unicode 标准。因为您的程序期望字符为 Unicode 格式,所以从系统获取的文本数据必须转换为 Unicode 格式,反之亦然。当文本文件的编码与 Java 虚拟机的默认文件编码匹配时,文本数据会自动转换为 Unicode 格式。您可以通过创建一个OutputStreamWriter
并询问其规范名称来确定默认文件编码:
OutputStreamWriter out = new OutputStreamWriter(new ByteArrayOutputStream()); System.out.println(out.getEncoding());
如果默认文件编码与您要处理的文本数据的编码不同,则必须自行进行转换。当处理来自其他国家或计算平台的文本时,您可能需要这样做。
本节讨论了用于将非 Unicode 文本转换为 Unicode 的 API。在使用这些 API 之前,您应该验证要转换为 Unicode 的字符编码是否受支持。支持的字符编码列表不是 Java 编程语言规范的一部分。因此,API 支持的字符编码可能因平台而异。要查看 Java 开发工具包支持的编码,请参阅支持的编码文档。
接下来的内容描述了将非 Unicode 文本转换为 Unicode 的两种技术。您可以将非 Unicode 字节数组转换为String
对象,反之亦然。或者您可以在 Unicode 字符流和非 Unicode 文本的字节流之间进行转换。
字节编码和字符串
本节向您展示如何将非 Unicode 字节数组转换为String
对象,反之亦然。
字符流和字节流
在本节中,您将学习如何在 Unicode 字符流和非 Unicode 文本的字节流之间进行转换。
字节编码和字符串
如果字节数组包含非 Unicode 文本,您可以使用 String
构造方法之一将文本转换为 Unicode。反之,您可以使用 String.getBytes
方法将 String
对象转换为非 Unicode 字符的字节数组。在调用这些方法时,您需要将编码标识符作为参数之一指定。
接下来的示例将字符在 UTF-8 和 Unicode 之间进行转换。UTF-8 是一种对 UNIX 文件系统安全的 Unicode 传输格式。示例的完整源代码在文件 StringConverter.java
中。
StringConverter
程序首先创建一个包含 Unicode 字符的 String
:
String original = new String("A" + "\u00ea" + "\u00f1" + "\u00fc" + "C");
当打印时,名为 original
的 String
如下所示:
AêñüC
要将 String
对象转换为 UTF-8,调用 getBytes
方法并指定适当的编码标识符作为参数。getBytes
方法以 UTF-8 格式返回一个字节数组。要从非 Unicode 字节的数组创建 String
对象,请使用带有编码参数的 String
构造方法。执行这些调用的代码被封装在一个 try
块中,以防指定的编码不受支持:
try { byte[] utf8Bytes = original.getBytes("UTF8"); byte[] defaultBytes = original.getBytes(); String roundTrip = new String(utf8Bytes, "UTF8"); System.out.println("roundTrip = " + roundTrip); System.out.println(); printBytes(utf8Bytes, "utf8Bytes"); System.out.println(); printBytes(defaultBytes, "defaultBytes"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
StringConverter
程序打印出 utf8Bytes
和 defaultBytes
数组中的值,以演示一个重要的观点:转换后的文本长度可能与源文本长度不同。一些 Unicode 字符转换为单个字节,而另一些转换为一对或三元组字节。
printBytes
方法通过调用在源文件 UnicodeFormatter.java
中定义的 byteToHex
方法来显示字节数组。以下是 printBytes
方法:
public static void printBytes(byte[] array, String name) { for (int k = 0; k < array.length; k++) { System.out.println(name + "[" + k + "] = " + "0x" + UnicodeFormatter.byteToHex(array[k])); } }
printBytes
方法的输出如下。请注意,只有第一个和最后一个字节,即 A 和 C 字符,在两个数组中是相同的:
utf8Bytes[0] = 0x41 utf8Bytes[1] = 0xc3 utf8Bytes[2] = 0xaa utf8Bytes[3] = 0xc3 utf8Bytes[4] = 0xb1 utf8Bytes[5] = 0xc3 utf8Bytes[6] = 0xbc utf8Bytes[7] = 0x43 defaultBytes[0] = 0x41 defaultBytes[1] = 0xea defaultBytes[2] = 0xf1 defaultBytes[3] = 0xfc defaultBytes[4] = 0x43
字符流和字节流
java.io
包提供了允许您在 Unicode 字符流和非 Unicode 文本的字节流之间进行转换的类。使用InputStreamReader
类,您可以将字节流转换为字符流。您可以使用OutputStreamWriter
类将字符流转换为字节流。以下图示说明了转换过程:
当您创建InputStreamReader
和OutputStreamWriter
对象时,您需要指定要转换的字节编码。例如,要将 UTF-8 编码的文本文件转换为 Unicode,您可以创建一个InputStreamReader
如下:
FileInputStream fis = new FileInputStream("test.txt"); InputStreamReader isr = new InputStreamReader(fis, "UTF8");
如果省略编码标识符,InputStreamReader
和OutputStreamWriter
将依赖于默认编码。您可以通过调用getEncoding
方法来确定InputStreamReader
或OutputStreamWriter
使用的编码,如下所示:
InputStreamReader defaultReader = new InputStreamReader(fis); String defaultEncoding = defaultReader.getEncoding();
下面的示例向您展示如何使用InputStreamReader
和OutputStreamWriter
类执行字符集转换。此示例的完整源代码在StreamConverter.java
中。此程序显示日文字符。在尝试之前,请验证系统上是否已安装适当的字体。如果您使用与版本 1.1 兼容的 JDK 软件,请复制font.properties
文件,然后用font.properties.ja
文件替换它。
StreamConverter
程序将String
对象中的一系列 Unicode 字符转换为以 UTF-8 编码的字节的FileOutputStream
。执行转换的方法称为writeOutput
:
static void writeOutput(String str) { try { FileOutputStream fos = new FileOutputStream("test.txt"); Writer out = new OutputStreamWriter(fos, "UTF8"); out.write(str); out.close(); } catch (IOException e) { e.printStackTrace(); } }
readInput
方法从由writeOutput
方法创建的文件中读取以 UTF-8 编码的字节。InputStreamReader
对象将 UTF-8 编码的字节转换为 Unicode,并以String
形式返回结果。readInput
方法如下:
static String readInput() { StringBuffer buffer = new StringBuffer(); try { FileInputStream fis = new FileInputStream("test.txt"); InputStreamReader isr = new InputStreamReader(fis, "UTF8"); Reader in = new BufferedReader(isr); int ch; while ((ch = in.read()) > -1) { buffer.append((char)ch); } in.close(); return buffer.toString(); } catch (IOException e) { e.printStackTrace(); return null; } }
StreamConverter
程序的main
方法调用writeOutput
方法创建一个以 UTF-8 编码的字节文件。readInput
方法读取相同的文件,将字节转换回 Unicode。以下是main
方法的源代码:
public static void main(String[] args) { String jaString = new String("\u65e5\u672c\u8a9e\u6587\u5b57\u5217"); writeOutput(jaString); String inputString = readInput(); String displayString = jaString + " " + inputString; new ShowString(displayString, "Conversion Demo"); }
原始字符串(jaString
)应与新创建的字符串(inputString
)完全相同。为了显示这两个字符串相同,程序将它们连接起来,并使用 ShowString
对象显示它们。ShowString
类使用 Graphics.drawString
方法显示字符串。此类的源代码在 ShowString.java
中。当 StreamConverter
程序实例化 ShowString
时,会出现以下窗口。显示的字符重复验证了这两个字符串是相同的:
规范化文本
原文:
docs.oracle.com/javase/tutorial/i18n/text/normalizerapi.html
规范化是一种过程,通过该过程您可以对文本执行某些转换,使其在以前可能无法协调的方式中协调。比如说,您想要搜索或排序文本,在这种情况下,您需要将该文本规范化以考虑应表示为相同文本的代码点。
什么可以被规范化?当您需要转换带有变音符号的字符,更改所有字母大小写,分解连字,或将半角片假名字符转换为全角字符等时,规范化是适用的。
根据Unicode 标准附录#15,规范化器的 API 支持java.text.Normalizer.Form
中定义的以下四种 Unicode 文本规范化形式:
- 规范化形式 D(NFD):规范分解
- 规范化形式 C(NFC):规范分解,然后规范组合
- 规范化形式 KD(NFKD):兼容性分解
- 规范化形式 KC(NFKC):兼容性分解,然后规范组合
让我们看看拉丁小写字母"o"带分音符号如何通过使用这些规范化形式进行规范化:
原始单词 | NFC | NFD | NFKC | NFKD |
“schön” | “schön” | “scho\u0308n” | “schön” | “scho\u0308n” |
您可以注意到在 NFC 和 NFKC 中原始单词保持不变。这是因为在 NFD 和 NFKD 中,复合字符被映射到它们的规范分解形式。但是在 NFC 和 NFKC 中,如果可能的话,组合字符序列被映射到复合字符。对于分音符号,没有复合字符,因此在 NFC 和 NFKC 中保持分解状态。
在稍后表示的代码示例NormSample.java
中,您还可以注意到另一个规范化特性。半角和全角片假名字符将具有相同的兼容性分解,因此它们是兼容性等效的。但是,它们不是规范等效的。
要确保您确实需要规范化文本,您可以使用isNormalized
方法来确定给定的字符序列是否已规范化。如果此方法返回 false,则意味着您需要规范化此序列,并且应使用normalize
方法,该方法根据指定的规范化形式规范化char
值。例如,要将文本转换为规范分解形式,您将需要使用以下normalize
方法:
normalized_string = Normalizer.normalize(target_chars, Normalizer.Form.NFD);
此外,规范化方法会将重音符号重新排列为正确的规范顺序,因此您无需担心自行重新排列重音符号。
以下示例表示一个应用程序,使您能够选择规范化形式和模板进行规范化:
注意: 如果您看不到 applet 运行,请安装至少 Java SE Development Kit (JDK) 7 版本。
这个 applet 的完整代码在 NormSample.java
中。
使用 JTextComponent 类处理双向文本
本节讨论如何使用JTextComponent
类处理双向文本。双向文本是包含从左到右和从右到左两个方向运行的文本。双向文本的一个示例是包含从右到左运行的阿拉伯文本(包含从左到右运行的数字)。显示和管理双向文本更加困难;但是JTextComponent
会为您处理这些问题。
下面涵盖了以下主题:
- 确定双向文本的方向性
- 显示和移动插入符
- 命中测试
- 高亮选择
- 设置组件方向
欲了解更多信息,或者如果您想更好地控制处理这些问题,请参阅处理双向文本在 2D 图形教程中。
确定双向文本的方向性
示例BidiTextComponentDemo.java
,基于TextComponentDemo.java
,在JTextPane
对象中显示双向文本。在大多数情况下,Java 平台可以确定双向 Unicode 文本的方向性:
在 JTextComponent 对象中明确指定文本运行方向
您可以指定JTextComponent
对象的Document
对象的运行方向。例如,以下语句指定JTextPane
对象textPane
中的文本从右到左运行:
textPane.getDocument().putProperty( TextAttribute.RUN_DIRECTION, TextAttribute.RUN_DIRECTION_RTL);
或者,您可以根据语言环境指定特定 Swing 组件的组件方向。例如,以下语句指定对象textPane
的组件方向基于 ar-SA 语言环境:
Locale arabicSaudiArabia = new Locale.Builder().setLanguage("ar").setRegion("SA").build(); textPane.setComponentOrientation( ComponentOrientation.getOrientation(arabicSaudiArabia));
因为阿拉伯语的运行方向是从右到左,所以textPane
对象中包含的文本的运行方向也是从右到左。
有关更多信息,请参阅设置组件方向。
显示和移动插入符
在可编辑文本中,插入符用于图形表示当前插入点,即文本中新字符将插入的位置。在BidiTextComponentDemo.java
示例中,插入符包含一个小三角形,指向插入字符将显示的方向。
默认情况下,JTextComponent
对象创建一个键映射(类型为Keymap
),该键映射作为所有JTextComponent
实例共享的默认键映射。键映射允许应用程序将按键绑定到操作。默认键映射(用于支持插入符移动的JTextComponent
对象)包括将插入符向前和向后移动与左右箭头键绑定,从而支持通过双向文本移动插入符。
点击测试
通常,设备空间中的位置必须转换为文本偏移量。例如,当用户在可选择文本上单击鼠标时,鼠标的位置将被转换为文本偏移量,并用作选择范围的一端。从逻辑上讲,这是定位插入符的逆过程。
您可以将插入符监听器附加到JTextComponent
的实例上。插入符监听器使您能够处理插入符事件,这些事件发生在插入符移动或文本组件中的选择更改时。您可以使用addCaretListener
方法附加插入符监听器。有关更多信息,请参见如何编写插入符监听器。
高亮选择
一段选定的字符范围在图形上由一个高亮区域表示,该区域是以反色或不同背景颜色显示字形的区域。
JTextComponent
对象实现了逻辑高亮。这意味着选定的字符在文本模型中始终是连续的,而高亮区域可以是不连续的。以下是逻辑高亮的示例:
设置组件方向
Swing 的布局管理器了解区域设置如何影响用户界面;不需要为每个区域设置创建新的布局。例如,在文本从右到左流动的区域,布局管理器将以相同的方向排列组件。
示例 InternationalizedMortgageCalculator.java
已本地化为英语,美国;英语,英国;法语,法国;法语,加拿大;以及阿拉伯语,沙特阿拉伯。
以下示例使用 en-US 区域设置:
以下示例使用 ar-SA 区域设置:
请注意,组件的布局与相应区域设置的方向相同:en-US 为从左到右,ar-SA 为从右到左。示例 InternationalizedMortgageCalculator.java
调用方法 applyComponentOrientation
和 getOrientation
来指定其组件的方向:
private static JFrame frame; // ... private static void createAndShowGUI(Locale currentLocale) { // Create and set up the window. // ... // Add contents to the window. // ... frame.applyComponentOrientation( ComponentOrientation.getOrientation(currentLocale)); // ... }
示例 InternationalizedMortgageCalculator.java
需要以下资源文件:
resources/Resources.properties
resources/Resources_ar.properties
resources/Resources_fr.properties
Java 中文官方教程 2022 版(三十)(4)https://developer.aliyun.com/article/1487967