警告:AI 生成内容 仅有简单校对
因本节篇幅过长(甚至远超许可证),仅放出 Google 翻译给出的结果,未经严格校对和修正,也没有整理术语纳入术语表。不过,即便只是机器翻译,也会比浏览器自带的翻译插件效果更好,因为我的确简单地调整了一下。如有需要,另请参阅 原文。
ICU 设计
概述
本章讨论 ICU 设计结构、ICU 版本支持以及 C++ 中命名空间的引入。
Java 和 ICU 基本设计结构
JDK 国际化组件和 ICU 组件在以下方面共享相同的通用基本架构:
由于编程语言的限制,ICU4C 中的一些设计功能不在 Java 开发工具包 (JDK) 中。这些功能包括以下内容:
区域设置
区域设置 ID 由语言、国家/地区和变体信息组成。以下链接提供了有关 ISO 标准的其他有用信息:ISO-639,和 ISO 国家代码,ISO-3166。例如,意大利语、意大利和欧元指定为 it_IT_EURO 。
数据驱动服务
数据驱动的服务通常使用区域设置数据的资源包。这些服务将密钥映射到数据。这些资源不仅设计用于管理系统区域设置信息,还用于管理特定于应用程序的或一般服务数据。 ICU 支持字符串、数字和二进制数据类型,并且可以构建为嵌套数组和表。
结果如下:
- 服务使用的数据可以在编译时或运行时构建。
- 为了高效加载,系统数据被预编译为.dll文件或可映射到内存的文件。
- 无需更改源代码即可添加和修改服务数据。
ICU 线程模型和开闭模型
“开闭”模型支持多线程。它使 ICU 用户能够在不同的区域设置中使用相同类型的服务,无论是在同一线程中还是在不同线程中。
例如,一个线程可以为不同的语言打开多个整理器,并且不同的线程可以同时为同一语言环境使用不同的整理器。可以共享常量数据,以便仅为每个编辑器分配当前状态。
ICU 线程模型旨在避免资源争用,并使您能够在同一线程中同时使用多个区域设置的服务。 ICU 线程模型与 ICU 架构的其余部分一样,与 Java™ 中的国际服务使用的模型相同。
当您使用排序规则等服务时,客户端使用 ID(通常是区域设置)打开该服务。该服务分配一小块内存用于服务状态,并带有指向支持该服务的共享只读数据的指针。 (在 Java 中,调用 getInstance() 来创建对象;在 C++ 中,调用 createInstance()。ICU 使用 C 语言中的 open 和 close 比喻,因为它对 C 程序员来说更熟悉。)
如果打开服务时未提供区域设置,ICU 将使用默认区域设置。一旦服务打开,更改默认区域设置就不起作用。因此,默认语言环境和开放服务之间不能有任何线程同步。
当您为同一区域设置打开第二个服务时,另一小块内存将用于服务的状态,其中的指针指向相同的共享只读数据。因此,大部分内存使用是共享的。当任何服务关闭时,内存块就会被释放。指向相同共享数据的其他连接保持有效。
对于相同区域设置或不同区域设置,可以在同一线程或不同线程中打开任意数量的服务。
线程安全 const API
在最近的 ICU 版本中,我们致力于使任何服务对象线程安全(可并发使用)只要所有线程仅使用 const API:在 C++ 中声明为 const 的 API,或在 C 中接受 const this-like 服务指针的 API,或在 Java 中是 “逻辑上const” 的 API。这是对原始 Java 或 ICU 线程模型的增强。(最初,即使仅并发使用 const API 也不是线程安全的。)
但是,您不能同时在两个线程中使用对开放服务对象的引用如果其中任何一个线程调用任何非 const API。单个开放服务对象对于并发“写入”来说不是线程安全的。相反,对于非常量使用,您必须使用克隆函数创建所需服务的副本,然后将此副本传递给第二个线程。此过程允许您在不同线程中使用相同的服务,但避免任何线程同步或死锁问题。
可冻结
一些类还实现了 Freezable 接口(或 C++ 中的类似模式),例如 UnicodeSet 或 Collator:通常开始可变的对象可以被设置然后“冻结”,这使得它不可变,因此可以同时使用,因为所有非常量 API 都被禁用。冻结的物体永远无法“解冻”。例如,可以创建一个“Collator”,设置各种属性,然后冻结,然后从多个线程中使用它来比较字符串并获取排序键。
克隆与开放
克隆操作的设计速度比使用初始参数重新打开服务并复制源状态要快得多。(对于 C++ 和 Java 中的对象,克隆功能也比尝试重新创建服务安全得多,因为您获得了正确的子类。)克隆服务后,更改将不会影响原始源服务,反之亦然。
因此,正常的操作模式是:
- 打开具有给定区域设置的服务。
- 根据需要使用该服务。但是,不要在紧密的循环中持续打开和关闭服务。
- 如果需要在另一个线程中并行使用服务,请克隆该服务。
- 关闭您打开的所有克隆以及所拥有的服务的所有实例。
👉 注意:这些服务实例可以按任何顺序关闭。上述步骤是作为示例进行的。
克隆定制
通常,ICU 提供的服务涵盖绝大多数用途。然而,在某些情况下,需要针对新的区域设置定制服务。 ICU(和 Java)使您能够创建定制服务。例如,您可以通过合并法语和阿拉伯语的规则来创建 RuleBasedCollator ,以获得自定义的法语-阿拉伯语排序序列。通过合并这些规则,指针不再指向线程之间共享的只读表。相反,该指针指向特定于您的特定开放服务的表。如果克隆开放服务,则会复制该表。当您关闭服务时,该表将被销毁。
对于某些服务,ICU 提供注册服务。您可以在一个 ID 下注册定制的开放服务;即使在关闭原始服务后仍保留该服务的副本。该线程或其他线程中的客户端可以通过使用该 ID 打开来重新创建服务的副本。
ICU 可以缓存服务实例。因此,应在启动期间完成注册,然后再通过区域设置 ID 开启服务。
这些注册不是持久的;一旦你的程序结束,ICU 就会刷新所有的注册。虽然您仍然可能拥有数据表的多个副本,但从注册 ID 创建服务比从规则创建服务要快。
👉 注意:要解决缺乏持久注册的问题,请查询服务以获取用于创建它的参数,然后将这些参数存储在磁盘上的文件中。
对于 ID 为 locale 的服务(例如排序规则),注册的 ID 也必须为 locale。对于那些跨区域设置的服务(如音译或时区),ID 可以是任何字符串。
该模型未来的预期增强功能包括:
- 通过对这些表进行引用计数,让自定义服务共享数据表。这将减少内存消耗并加速克隆操作(性能增强主要适用于使用相同定制服务的多个线程)。
- 扩大所有国际业务的注册范围。
- 允许服务的持久注册。
每个客户端区域设置 ID 与每个线程区域设置 ID
某些应用程序环境通过设置每个线程(或每个进程)区域设置 ID 进行操作,然后在处理期间不将区域设置 ID 作为参数传递。如果此使用模型与多线程服务器中的 ICU 一起使用,则可能会导致 ICU 被请求不断打开、使用然后关闭服务对象。相反,建议将与每个客户端关联的区域设置 ID 与其他每个客户端数据以及客户端可能使用的任何服务对象(例如整理器或格式化程序)一起存储。如果涉及单个客户端的操作是短暂的,则保留根据区域设置组织的服务对象池可能会更有效。然后,如果特定区域设置的格式化程序需求量很大,则可以使用该格式化程序,然后将其返回到池中。
ICU 内存使用情况
ICU4C API 旨在允许其库与应用程序使用单独的堆。这是通过仅使用 ICU4C 库函数提供分配和释放 ICU4C 拥有的对象的函数来实现的。有关更多详细信息,请参阅 编码指南 中的内存使用部分。
ICU4C 初始化和终止
ICU 库在使用前通常不需要任何显式初始化。应用程序只需以通常的方式调用任何 ICU API 即可开始使用。然而,有一些函数会影响 ICU 的配置,如果使用这些函数,则必须在进程中使用 ICU 之前首先调用这些函数。这些概述如下:
u_setMemoryFunctions()。此函数用应用程序提供的替代版本替换了 ICU 使用的标准库堆分配函数。如果需要,必须在使用 ICU 之前先调用 u_setMemoryFunctions()。此功能并不常用。ICU 数据定位函数
u_setCommonData()u_setDataDirectory()和u_setAppData()。当 ICU 配置为直接从文件加载数据而不是从默认数据 DLL 中获取数据并且文件不在默认位置时,将需要其中一个或多个函数。同样,这种情况并不常见。请参阅 ICU 数据。健全性检查 ICU 是否正常运行并能够访问数据。这很重要,因为确实会发生导致 ICU 无法加载其数据的配置或安装问题,并且由此产生的故障可能会令人困惑。由于并非所有 ICU API 都有 UErrorCode 参数,因此在缺乏数据的情况下,它们有时可能会默默地返回不正确的结果。
函数
ulocdata_getCLDRVersion()是合适的;它体积小,重量轻,需要数据,在没有数据的情况下报告错误。
当应用程序终止时,它应该调用函数 u_cleanup() ,该函数释放 ICU 库内部保留的所有堆存储和其他系统资源。虽然 u_cleanup() 的使用不是严格要求的,但如果不调用它,将导致内存泄漏检查工具报告 ICU 库所占用的资源存在问题。
在调用 u_cleanup() 之前,必须删除应用程序创建的所有 ICU 对象,并且必须关闭所有 ICU 服务(纯 C API)。
对于某些平台,配置选项 --enable-auto-cleanup 或将选项 UCLN_NO_AUTO_CLEANUP 定义为 0,将添加在卸载共享库时自动清理 ICU 的代码。请参阅 ucln_imp.h 中的注释。
C++ 静态初始化和销毁
ICU 库本身不依赖于 C++ 静态初始化程序,这意味着应用程序不会因使用 ICU 而遇到初始化顺序问题。
然而,对于在 C++ 静态初始化时使用 ICU 的应用程序来说,存在一些重大限制:
u_setMemoryFunctions()和数据定位函数(如果需要)仍然必须在使用 ICU 之前调用。其中包括静态对象构造过程中的任何使用。- 只有在删除所有其他使用 ICU 的对象后才能调用
u_cleanup()。然而,找到合适的时间和地点来调用u_cleanup()可能很困难。有关静态初始化和销毁的顺序,请参阅 C++ 文献。 - 销毁作用域为代码块的静态对象。根据 C++ 的约定,这些都是在首次进入代码块时延迟初始化的,因此在静态初始化期间不会出现问题。但是当程序终止时会发生对象销毁,从而留下了在哪里调用
u_cleanup()的问题,如上所述。
动态加载和卸载 ICU
应用程序可以安排在需要时动态加载 ICU 库,并在完成时卸载它,根据需要重复该过程。加载和卸载以及访问此类库的具体细节取决于操作系统。
要以这种方式使用 ICU,在卸载之前,必须关闭或删除所有 ICU 对象和服务,并且必须调用 u_cleanup()。
在 Windows 上,ICU 的加载和卸载永远不应在 DllMain 内完成。加载 ICU 库之一可能会导致加载其他库或文件,从而导致潜在的死锁。
在多线程环境中初始化 ICU
在一种特殊情况下,需要额外小心才能安全初始化 ICU。仅当以下所有情况发生时才会出现这种情况:
- 应用程序主程序是用纯 C 编写的,而不是C++。
- 应用程序是多线程的,进程内第一次使用 ICU 可能同时发生在多个线程中。
- 当主程序不是 C++ 时,应用程序将在不处理库中的 C++ 静态构造函数的平台上运行。已知表现出这种行为的平台是 Mac OS X 和 HP/UX。正确处理 C++ 库的平台包括 Windows、Linux 和 Solaris。
为了在满足上述所有条件时安全地初始化 ICU 库,应用程序必须在开始多线程使用 ICU 之前显式安排从单线程首次使用 ICU。用于此目的的一个方便的 ICU 操作是 uloc_getDefault() ,在头文件 unicode/uloc.h 中声明。
👉 注意:这种情况的状况需要进一步调查。请参阅 Issue ICU-21380
错误处理
为了使 ICU 最大限度地提高可移植性,此版本仅包含在较旧的 C++ 编译器上正确编译并提供可用的 C 接口的 C++ 语言子集。因此,代码或 API 中没有使用 C++ 异常机制。
为了可靠地传达错误并支持多线程,该版本使用错误代码参数机制。每个可能失败的函数都通过引用获取错误代码参数。该参数始终是该函数列出的最后一个参数。
UErrorCode 参数被定义为枚举类型。零表示没有错误,正值表示错误,负值表示无错误状态代码。提供宏( U_SUCCESS 和 U_FAILURE )来检查错误代码。
UErrorCode 参数是一个输入输出函数。每个函数在执行任何其他任务之前都会测试错误代码,如果产生 FAILURE 错误代码,则立即退出。如果该函数稍后失败,它将适当地设置错误代码并退出,而不执行任何其他工作,除了需要执行的任何清理之外。如果函数遇到它想要发出信号的非错误条件,例如在转换中“遇到未映射的字符”,则函数会相应地设置错误代码并继续。否则,该函数将保持错误代码不变。
通常,只有不带 UErrorCode 参数但调用带 UErrorCode 参数的函数才必须声明变量。几乎所有采用 UErrorCode 参数的函数,以及调用其他函数的函数,只需将它们传递给它们调用的函数的错误代码传播即可。声明新 UErrorCode 参数的函数必须在调用任何其他函数之前将其初始化为 U_ZERO_ERROR 。
ICU 使您能够连续调用多个函数(带有错误代码),而无需在每个函数之后检查错误代码。每个函数通常必须在执行任何其他处理之前检查错误代码,因为它应该在收到错误代码后立即停止。沿调用链向下传播错误代码参数可以使程序员不必在每个实例中声明该参数,并且还可以更紧密地模仿 C++ 异常协议。
可扩展性
ICU 有 3 个主要的可扩展性元素:
- 数据可扩展性:用户安装新的区域设置或转换数据以增强现有的 ICU 支持。有关更多详细信息,请参阅打包工具(🚧 待办事项:需要链接)章节以获取更多信息。
- 代码可扩展性:类、数据和设计是完全可扩展的。这种可扩展性的示例包括
BreakIterator、RuleBasedBreakIterator和DictionaryBasedBreakIterator类。 - 错误处理可扩展性:有必要时可以使用一些机制来增强内置错误处理。例如,您可以设计并创建自己的错误发生时的转换回调函数。有关更多信息,请参阅 Conversion 章节回调部分。
资源包继承模型
资源包是一组 <key,value> 对,提供从键到值的映射。一个给定的程序可以有不同的资源包集合;一组用于错误消息,一组用于菜单,依此类推。然而,该程序可以被组织为将其所有资源束组合成单个相关集。
该集合被组织成一棵树,“根”位于顶部,语言位于第一级,国家或地区位于第二级,以及这些级别以下的其他变体。该集合必须包含一个根,该根具有可供访问资源包的程序使用的所有密钥。
除了根之外,每个资源束都有一个直接父级。例如,如果存在资源包 X_Y_Z ,则必须存在资源包: X_Y 和 X 。每个子资源包都可以省略与其父资源对相同的任何 <key,value> 对。(强烈鼓励这种省略,因为它可以减少数据大小和维护工作)。它必须覆盖与其父项对不同的任何 <键, 值> 对。如果您有一个针对区域设置 ID language_country_variant 的资源包,那么您还必须有一个针对 ID language_country 的资源包和一个针对 ID language 的资源包。
如果程序在子资源包中找不到密钥,则可以假设它与父资源包具有相同的密钥。默认区域设置对此没有影响。用于根的特定语言通常是英语,但这取决于开发人员的偏好。理想情况下,该语言应该包含最大限度地减少其子级覆盖它的需要的值。
仅当没有给定语言的资源包时,才使用默认区域设置。例如,可能没有意大利资源包。 (这与意大利资源包缺少特定键的情况非常不同。)当资源包丢失时,ICU 将使用父级,除非该父级是根。根是一个例外,因为根语言可能与其子语言完全不同。在这种情况下,ICU 使用修改后的查找和默认区域设置。以下是可用的不同查找方法:
查找链:搜索资源包。
en_US_<一些变体>
en_US
en
<默认语言>_<默认国家或地区>
<默认语言>
root查找链:在加载 en_US_<some-variant> 后搜索 <键, 值> 对。在这种情况下,ICU 不使用默认区域设置。
en_US_<一些变体>
en_US
en
root其他 ICU 设计原则
ICU 支持广泛的版本代码和数据更改,并引入了命名空间的使用。
ICU 中的版本号
版本更改会向客户显示 ICU 部分内容何时发生更改。ICU、它的组件(例如 Collator )、每个资源包(包括所有语言环境数据资源包)、资源包中的各个标记项目都有自己的版本号。随着库的迭代升级,版本号在数值和字典序上都会递增。
所有版本号都在应用程序编程接口 (API) 中使用,并具有 UVersionInfo 结构。 UVersionInfo 结构是一个由四个无符号字节组成的数组。这些字节是:
- 主版本号
- 次版本号
- 小版本号
- 微版本号
可以使用二进制比较( memcmp )来比较两个 UVersionInfo结构,以查看哪个更大或更新。不同服务的版本号可能不同。例如,不要将 ICU 库版本号与 ICU 整理器版本号进行比较。
可以使用 u_versionToString() 和 u_versionFromString() 函数将 UVersionInfo 结构与点分整数形式的字符串表示形式(例如“1.4.5.0”)相互转换。字符串表示形式可能会省略尾随零。
版本号的解释取决于所描述的内容。
ICU 发行版本号(ICU 49 及更高版本)
第一个版本号字段包含 ICU 版本号,例如 49。每个新版本可能包含新功能、新区域设置数据和修改的行为。(有关 ICU 二进制兼容性 的更多信息,请参阅下文)
第二个字段为 1,表示初始版本(例如 49.1)。对于二进制兼容的维护版本,第二个字段(有时是第三个字段)会递增。
- 对于仅 C 或 J 的维护版本,第三个字段递增(例如,ICU4C 49.1.1)。
- 对于 C 和 J 的共享更新,第二个字段递增到 2 或更高(例如,ICU4C 和 ICU4J 49.2)。
(开发期间第二个字段为 0,在此期间第三个字段中具有里程碑编号。例如,49.0.1 表示 49 个里程碑 1。)
ICU 发行版本号(ICU 1.4 至 ICU 4.8)
在早期版本中,前两个版本字段一起指示 ICU 版本,例如 4.8。第三个字段对于初始版本为 0,对于二进制兼容(仅错误修复)维护版本(例如 4.8.1)为 1 或更高。第四个字段仅用于特定于 Java、C++ 或 ICU-in-Eclipse 之一的更新。
第二个版本字段对于正式版本(“参考版本”)(例如 1.6 或 4.8)为“偶数”,在开发过程中为“奇数”(未发布的不稳定快照版本;例如 4.7)。在开发过程中,第三个字段包含里程碑编号(例如,4.7.1 表示 4.8 里程碑 1)。对于非常旧的 ICU 代码,我们发布了带有奇数第二字段数字(例如 1.7)的半正式“增强”版本。
库文件名和其他一些内部用途已经使用了前两个字段的串联(4.8 为“48”)。
资源包和元素
存储在资源包中的数据带有版本号标记。资源包可以包含名为“Version”的标记字符串,该字符串以点分整数格式声明版本号。例如,
en {
Version { "1.0.3.5" }
...
}资源包可以省略“版本”元素,因此将沿通常的链继承版本。例如,如果资源包 en_US 不包含“version”元素,它将从父 en 元素继承“1.0.3.5”。如果继承一直传递到根资源包并且它不包含“版本”资源,则资源包接收默认版本号 0。
资源包内的元素还可以包含版本号。例如:
be {
CollationElements {
Version { "1.0.0.0" }
...
}
}在此示例中,CollationElements 数据的版本为 1.0.0.0。该元素版本与捆绑包的版本无关。
内部版本号
在内部,数据文件带有格式和其他版本号。这些版本号确保 ICU 可以使用该数据文件。解释完全取决于数据文件类型。通常,格式版本中的主编号保持不变,以实现对数据文件格式的向后兼容更改。对于不违反数据文件的向后兼容性的添加,次要格式版本号会递增。
组件版本号
可以使用以下方式找到 ICU 组件版本号:
u_getVersion()返回 C++ 中 ICU 的整体版本号。在 C 语言中,ucol_getVersion()返回 ICU 的整体版本号。ures_getVersion()和ResourceBundle::getVersion()返回 ResourceBundle 的版本号。这是整个捆绑包的数据版本号,并且受继承影响。u_getUnicodeVersion()和Unicode::getUnicodeVersion()返回 ICU 下的 Unicode 字符数据的版本号。此版本反映了 Unicode 版本的编号。有关详细信息,请参阅 http://www.unicode.org/。- C++ 中的
Collator::getVersion()和 C 中的ucol_getVersion()返回 Collator 的版本号。这是校对代码和算法的代码版本号。它是排序规则实现的版本号、Unicode 排序规则算法数据(用于区域设置的特定排序规则元素中未提及的字符的数据)和排序规则元素的组合。
配置与管理
ICU 2.0 的一个主要新功能是能够使用同一程序链接到不同版本的 ICU。例如,使用此新功能,程序可以继续使用 ICU 1.8 排序规则,同时使用 ICU 2.0 进行其他服务。现在还可以根据需要卸载 ICU,以释放资源,然后在需要时重新加载。
C++ 中的命名空间
ICU 2.0 引入了 C++ 命名空间的使用,以避免 ICU 导出符号和其他库之间的命名冲突。所有公共 ICU C++ 类都在 icu_VersionNumber:: 命名空间中定义,该命名空间也别名为命名空间“icu”。从 ICU 2.0 开始,默认情况下任何公共 ICU C++ 标头都包含 using namespace icu_VersionNumber 语句。这是为了向后兼容,应该关闭以支持显式使用 icu::UnicodeString 等(请参阅 如何使用 ICU)。(如果关闭入口点重命名,则仅使用未版本化的“icu”命名空间。)
从 ICU 49 开始,ICU4C 需要命名空间支持。
库依赖项 (C++)
有时查看公共 ICU API 和 ICU 库之间的依赖关系图很有用。此图表对于刚接触 ICU 的人员或只需要某些 ICU 库的人员很有用。
🚧 TODO:依赖关系图当前不可用。
以下是有关图表需要了解的一些事项。
- 它概述了 ICU 库依赖项。
- 为了清楚起见,省略了内部依赖项(例如互斥 API)。
- 为了清晰起见,类似的 API 被集中在一起(例如格式化)。其中一些依赖项详细信息可以从 ICU API 参考中查看。
- 每个 API 的描述可以在我们的 ICU API参考 中找到。
代码依赖关系 (C++)
从 ICU 49 开始,代码文件(从 .c 或 .cpp 编译的 .o 文件)的依赖项记录在 source/test/depstest/dependency.txt。相邻的 Python 代码用于解析此文件并 验证 它是否与实际依赖项匹配代码文件。
依赖项列表可用于构建子集库。此外,通过减少库内依赖性,静态链接的 ICU 代码的代码大小也得到了减小。
ICU API 类别
头文件和类文件中定义的 ICU API 要么是“外部”,要么是“内部”。外部 API 供应用程序使用,而内部 API 只能在 ICU 内使用。 API 被标记以指示它们是外部的还是内部的,如下所示。每个外部 API 都有一个生命周期标签,请参见下文。
外部 ICU4C API
外部 ICU4C API 是
- 在 unicode 文件夹的头文件中声明,并在构建或安装时导出到
include/unicode文件夹 - 当 C++ 类成员为
public或protected时 - 没有
@internal标签
例外:布局引擎头文件不在 unicode 文件夹中,尽管公共头文件仍然在构建或安装时复制到 include/unicode 文件夹中。外部布局引擎 API 具有生命周期标签,而不是 @internal 标签。
外部 ICU4J API
外部 ICU4J API 是
- 在 ICU4J 核心包之一(
com.ibm.icu.langcom.ibm.icu.mathcom.ibm.icu.textcom.ibm.icu.util)中声明。 public或protected类成员public或protected包含的类- 没有
@internal标签
“系统”API
“系统”API 是外部 API,仅用于系统级代码的特殊用途,例如 u_cleanup() 。普通用户不应该使用它们,尽管它们是公开的并且受支持。除了所有外部 API 具有的生命周期标签之外,系统 API 还具有 @system 标签(见下文)。
内部 API
所有不符合上述任何描述的 API 都是内部 API,这意味着它们仅供 ICU 内部使用,并且可能随时更改,恕不另行通知。其中一些是公共 C++ 或 Java 类的成员函数,并且由于实现原因“技术上是公共的,但逻辑上是内部的”;通常是因为编程语言没有提供足够的访问控制(没有笨拙的机制)。在这种情况下,此类 API 具有 @internal 标签。
ICU API 兼容性
随着 ICU 的发展,它添加了外部 API:函数、类、常量等。有时还需要删除或更改外部 API。为了完成这项工作,我们使用以下流程:
对于所有 API 变更(以及重大或有争议或困难的实施变更),我们使用提案来宣布和讨论它们。提案只是发送给 icu-design 邮件列表的电子邮件,其中详细说明了建议更改的内容,有效期通常为一周。这使所有邮件列表成员都有机会查看即将发生的更改并进行讨论。提案经常会因讨论而发生重大变化。大多数提案最终会在名单成员中达成共识;否则,由 ICU-TC 决定如何处理。如果 API 的添加或更改会对您产生影响,请订阅 icu-design 邮件列表。
当新 API添加到 ICU 时,它在 API 文档中被标记为带有 @draft ICU x.y 标签的草稿,其中 x.y 是引入 API 签名时的 ICU 版本或最后更改。 API 草案不保证稳定!虽然我们不会无端改动,但有时 API 草案在实际应用中并不能令人满意,可能需要更改甚至删除。 “草案”API 的更改须遵守上述提案流程。
当 @draft ICU x.y API 更改时,它必须保持“@draft”并且必须更新其版本号。
在 ICU4J 3.4.2 及更早版本中, @draft API 还标有 Java 的 @deprecated 标签,以便编译器在客户端代码中使用草稿 API 时会被标记。 @deprecated 标签的这些使用通过注释“这是 API 草案,可能会在 ICU 的未来版本中发生变化”来表示。许多客户发现这令人困惑和/或不受欢迎,因此 ICU4J 3.4.3 默认情况下不再使用 @deprecated 标签标记草稿 API。对于喜欢早期行为的客户,ICU4J 提供了一个 ant 构建目标 restoreDeprecated ,它将更新源文件以使用 @deprecated 标签。然后客户可以像往常一样重建 ICU4J jar。
当某个 API 被判断为稳定且至少在一个 ICU 版本中未发生更改时,它会在 API 文档中使用 @stable ICU x.y** 标签重新标记为稳定。预计稳定的 API 将以这种形式长期可用。 ICU 版本 x.y 表示上次引入或更改 API 签名的时间。从 @draft ICU x.y 升级到 @stable ICU x.y 不得更改 x.y 版本号。
如果我们认为它们必须稳定,我们偶尔会例外,并允许在 x.y 版本本身中添加标记为 @stable ICU x.y API 的新 API。我们可能会对反映 1:1 Unicode 属性别名和属性值别名的枚举常量执行此操作,以实现 x.y 版本中的 Unicode 升级。
我们有时会通过以兼容的方式更改签名来“扩展” @stable API 函数。例如,在 Java 中,我们可以将输入参数从 String 更改为 CharSequence 。在这种情况下,我们保留 @stable ,但更新 ICU 版本号,指示函数签名更改。
即使是稳定的 API 最终也可能需要被弃用或过时。强烈建议不要使用此类 API。通常,改进的 API 是在旧 API 弃用或废弃时引入的。
- 强烈建议不要使用已弃用的 API,但为了向后兼容而保留它们。这些标有
@deprecated ICU x.y Use u_abc() instead.之类的标签。ICU 版本 x.y 显示 API 首次被声明“已弃用”的 ICU 版本。 - 在 ICU4J 中,从版本 57 开始,添加了自定义 Javadoc 标签
@discouraged。虽然与@deprecated类似,但当 ICU 想要阻止使用特定 API 但 JDK 尚未弃用它或者 ICU 出于兼容性原因需要保留它时,就会使用它。这些标有@discouraged ICU x.y. Use u_abc() instead.。 - 过时的 API 是指那些继续保留将导致严重冲突或用户错误,或者继续支持将成为非常重大的维护负担的 API。我们尽一切努力将这些风险降到最低。过时的 API 标有
@obsolete ICU x.y. Use u_abc() instead since this API will be removed in that release.。x.y 表示我们计划在 ICU 版本 x.y 中删除它
稳定的 C 或 Java API 不会被淘汰,因为这样做会破坏 ICU 库的二进制兼容性。稳定的 API 可能会被弃用,但它们将保留在库中。
“过时的”API 将保持不变,直到在指定的 ICU 版本中删除为止,这通常是在 API 被宣布过时后一年。有时,我们仍然通过编译时开关使其可用一段时间,但停止维护它。在极少数情况下,由于命名冲突或严重缺陷,API 必须立即更换;在这种情况下,我们提供编译时开关( #ifdef 或其他机制)来选择旧的 API。
例如,以下是 API 在不同版本中的标记方式:
- 在 ICU 0.2 中:API 是在此版本中作为草案新引入的。
@draft ICU 0.2
f(x)- 在 ICU 0.4 中:草稿版本号已更新,因为签名已更改。
@draft ICU 0.4
f(x, y)- 在 ICU 0.6 中:API 从草稿升级到稳定版,但版本号没有改变,因为签名相同。
@stable ICU 0.4
f(x, y)- 在 ICU 1.0 中:API 以兼容的方式“扩展”。例如,将输入参数从 char 更改为 int 或从某个类更改为基类。签名已更改(因此我们更新了 ICU 版本号),但旧的调用代码继续保持不变(因此我们保留 @stable,如果是这样的话。)
@stable ICU 1.0
f(xbase, y)- In ICU 1.2: API 已降级为已弃用(或过时)状态。
@deprecated ICU 1.2 Use g(x,y,z) instead.
f(xbase, y)
// 或者,当计划在 ICU 1.4 中删除此 API 时:
@obsolete ICU 1.4. Use g(x,y,z) instead.
f(xbase, y)ICU 二进制兼容性
使用 ICU 作为操作系统级库
ICU4C 可以配置为在以下环境中用作系统库:使用某一版本的 ICU 构建的应用程序必须继续运行,而无需使用更高版本的 ICU 共享库进行更改。
以下是启用 ICU4C 二进制兼容性的要求:
- 应用程序必须仅使用标记为稳定的 API。
- 应用程序必须仅使用纯 C API,而不能使用 C++。
- 构建 ICU 时必须禁用功能重命名。
- 应用程序必须使用配置为二进制兼容性的 ICU 来构建。
- 使用 ICU 3.0 或更高版本。
- Provide both “common” and “i18n” libraries, or build a combined library.
仅限稳定 API。 ICU 库中标记为稳定的 API 将在该库的未来版本中得到维护。稳定的函数将继续以相同的签名和相同的含义存在,允许应用程序无需更改即可继续工作。
稳定的 API 不能保证 ICU 版本之间每个函数的结果始终完全相同(请参阅上面的 ICU 中的版本号 部分)。错误可能会得到修复。 Unicode 字符数据可能会随着 Unicode 标准的新版本而改变。区域设置数据可能会更新或更改,从而为格式化或排序等操作产生不同的结果。需要 ICU 结果精确的逐位、逐个错误兼容性的应用程序不应依赖于 ICU 版本间的二进制兼容性,而应链接到特定版本的 ICU。
要验证应用程序是否仅使用稳定的 API,请使用定义的 C 预处理器符号 U_HIDE_DRAFT_API 和 U_HIDE_DEPRECATED_API 来构建它。如果使用任何草稿、已弃用或过时的 API,这将产生构建错误。 ICU 的操作系统级安装可能会永久设置此选项。
仅限 C API。 只有普通 C API 在 ICU 版本之间保持兼容。不支持 C++ 二进制兼容性的原因主要是因为 C++ 语言和运行时环境的设计给这样做带来了极大的技术困难。稳定的 C++ API 是源兼容的,但是使用它们的应用程序在 ICU 版本之间移动时必须重新编译。
函数重命名已禁用。 函数重命名是一项 ICU 功能,它允许应用程序显式链接到 ICU 库的特定版本,并继续使用该版本,即使运行时环境中存在其他 ICU 版本也是如此。这与版本间的二进制兼容性完全相反——应用程序不能透明地更改 ICU 版本,而是明确绑定到一个特定版本。
默认情况下启用函数重命名,并且必须在 ICU 构建时禁用它才能启用发布以释放二进制兼容性。要禁用重命名,请使用配置选项
configure -–disable-renaming [other-configure-options](配置选项也可以传递给 runConfigureICU 脚本。)
为了实现版本间的二进制兼容性,ICU 必须使用 --disable-renaming 进行构建,并且应用程序必须使用由 --disable-renaming ICU 构建产生的标头和库进行构建
ICU 版本 3.0 或更高版本。 从 ICU 版本 3.0 开始,支持 ICU 版本的二进制兼容性。旧版本的 ICU(2.8 及更早版本)不提供版本之间的二进制兼容性。
同时提供“通用”和“i18n”库,或构建一个组合库。 服务或 API 从一个库转移到另一个库的情况很少见,但也有可能。例如,许多年前,我们将 BreakIterator API 从 i18n 移至通用,因此单词标题大小写函数不再需要单独的代码来查找标题大小写或单词中断机会。
最近,ListFormatter 从公共库转移到 i18n,其功能超越了原始模式,还支持 FieldPosition 和 FormattedValue 功能。
还有第三个“io”库。它的某些功能可能会转移到 i18n 或公共库中。 (一个可能的候选者可能是“operator<<(std::ostream&stream, const UnicodeString&s)”,尽管在撰写本文时还没有实际计划这样做。)
人们可以构建一个组合库,提供“common”和“i18n”库的导出,以便提供一个用于链接的库。
对于某些 API 和实现它的库之间存在密切关系的平台来说,这可能是需要的。例如,在 Windows 平台上,尝试查找已通过 LoadLibrary 或 GetProcAddress 方法移动的 API 将失败,除非您使用组合库。
链接多个版本的 ICU4C
本节旨在帮助正在实施或集成基于 ICU 的解决方案的软件开发人员,他们可能需要考虑同时在同一可执行文件(地址空间)中运行多个版本的 ICU。通常,鼓励 ICU 用户更新到最新的稳定版本。然而,在某些情况下,需要早期版本的行为,否则应用程序会将已经针对不同版本的 ICU 构建的代码链接在一起。
主版本号和次版本号是版本号中的第一个和第二个数字,以句点分隔。例如,在版本号 3.4.2.1、3.4.2 或 3.4 中,“3”是主要版本,“4”是次要版本。通常,ICU 采用“符号重命名”,例如 C 函数名称和 C++ 对象名称是 #define 来包含主编号和次编号。因此,例如,如果您的应用程序调用函数 ucnv_open() ,如果针对 ICU 3.4、3.4.2 甚至 3.4.2.1 进行编译,它将链接到 ucnv_open_3_4 。但是,如果针对 ICU 3.8 进行编译,相同的代码将链接到 ucnv_open_3_8 。类似地, UnicodeString 被重命名为 UnicodeString_3_4 等。这通常对用户来说是透明的,但是,如果您检查库或代码的符号,您将看到修改后的符号。
如果一个应用程序中链接了多个 ICU 版本,则需要链接每个版本的所有相关库,例如 common、i18n 和 data。 ICU 使用标准库重命名,例如,一个平台上的 libicuuc.so 实际上是 libicuuc.so.3.4 的符号链接。当使用多个 ICU 版本时,应用程序可能需要显式链接到正在使用的 ICU 的确切版本。
要禁用重命名,请使用传递给配置的 --disable-renaming 构建 ICU。或者,设置等效的 #define U_DISABLE_RENAMING 1 。必须在 ICU 构建和调用应用程序中禁用重命名。
ICU 数据兼容性
从 ICU 3.8 及更高版本开始,ICU 附带的数据库与具有相同主次版本或维护版本的 ICU 版本进行二进制兼容和结构兼容。这允许 ICU 的多个维护版本共享相同的数据,但通常应使用数据的最新维护版本。
数据的二进制兼容性是指包含语言环境数据、字符集转换表和 ICU 支持的其他文件格式的资源包二进制格式。这些二进制格式可由许多版本的 ICU 读取。例如,使用 ICU 3.6 编写的资源包可由 ICU 3.8 读取。
数据的结构兼容性是指ICU数据的结构内容。区域设置数据的结构可能在参考版本之间发生变化,但引用特定类型数据的键在维护版本之间是相同的。这意味着用于访问资源包内数据的资源密钥将在特定参考版本的维护版本之间工作。例如,ICU 3.8 日历将能够使用 ICU 3.8.1 数据,反之亦然;但是 ICU 3.6 可能无法读取 ICU 3.8 区域设置数据。通常,ICU 用户无法访问这些密钥,因为只有 ICU 实现才使用这些资源密钥。
数据库的内容可能会在 ICU 维护版本之间发生变化,并由于重要的更新和错误修复而给您带来不同的结果。重要更新的一个示例是当夏令时发生时国家或地区发生变化时的时区规则更新。因此,维护版本之间的结果可能会有所不同。
ICU4J 序列化兼容性
从 ICU4J 3.6 开始,实现 java.io.Serialized 的 ICU4J 稳定 API 类(标记为 @stable )支持由 ICU4J 3.6 或更新版本的 ICU4J 反序列化的序列化对象。某些类仅执行浅序列化,因此不能保证反序列化对象在 ICU4J 版本中的行为与原始对象完全相同。此外,当由于技术或其他原因难以在不同 ICU4J 版本之间维持某个类的序列化兼容性时,ICU 项目委员会可能会批准破坏。在这种情况下,解释兼容性问题的注释将发布在 ICU 公共邮件列表中,并记录在介绍不兼容性的新 ICU4J 版本的发行说明中。