国际化消息文件的查找规则有一些反直觉的地方:默认 Locale 会混进回退链,编码有静默回退,缓存永不过期。这篇用临时目录里的真实文件逐项验证。
[candidates]
zh-CN → [zh_CN_#Hans, zh__#Hans, zh_CN, zh, (root)]
zh-Hans-CN → [zh_CN_#Hans, zh__#Hans, zh_CN, zh, (root)]
zh-Hant-TW → [zh_TW_#Hant, zh__#Hant, zh_TW, zh, (root)]
fr-CA → [fr_CA, fr, (root)]
en → [en, (root)]
sr-Latn-RS → [sr_RS_#Latn, sr__#Latn, sr_RS, sr, (root)]
und → [(root)]
Control.getFallbackLocale(…, ja) 在默认 Locale = en_US 时 = en_US [fallback]
默认 Locale = en_US
getBundle(Msg, ja ) who → base [bundle locale=(root)]
getBundle(Msg, fr-CA ) who → français [bundle locale=fr]
getBundle(Msg, zh-CN ) who → 简体中文 [bundle locale=zh_CN]
getBundle(Msg, zh-TW ) who → 中文(zh) [bundle locale=zh]
getBundle(Msg, de-AT ) who → Deutsch [bundle locale=de]
getBundle(Msg, en ) who → base [bundle locale=(root)]
默认 Locale = fr
getBundle(Msg, ja ) who → français [bundle locale=fr]
getBundle(Msg, fr-CA ) who → français [bundle locale=fr]
getBundle(Msg, zh-CN ) who → 简体中文 [bundle locale=zh_CN]
getBundle(Msg, zh-TW ) who → 中文(zh) [bundle locale=zh]
getBundle(Msg, de-AT ) who → Deutsch [bundle locale=de]
getBundle(Msg, en ) who → français [bundle locale=fr]
默认 Locale = de
getBundle(Msg, ja ) who → Deutsch [bundle locale=de]
getBundle(Msg, fr-CA ) who → français [bundle locale=fr]
getBundle(Msg, zh-CN ) who → 简体中文 [bundle locale=zh_CN]
getBundle(Msg, zh-TW ) who → 中文(zh) [bundle locale=zh]
getBundle(Msg, de-AT ) who → Deutsch [bundle locale=de]
getBundle(Msg, en ) who → Deutsch [bundle locale=de]
Control.NO_FALLBACK,默认 Locale = fr,请求 ja → base
zh_CN 缺 onlyzh 时从父级 zh 取:只在 zh;缺 onlybase 时从根取:only in base
zh_CN.getKeys() = [onlybase, onlyzh, who];keySet() = [onlybase, onlyzh, who];containsKey(onlyfr) = false
缺失键:MissingResourceException: Can't find resource for bundle java.util.PropertyResourceBundle, key nope(className=java.util.PropertyResourceBundle,key=nope)
缺失 bundle:Can't find bundle for base name NoSuchBundle, locale en_US [encoding]
UTF-8 文件:name = 中文 é ü 😀;escaped(\uXXXX)= 中文
ISO-8859-1 字节(含 0xE9、0xEF,不是合法 UTF-8):name = café naïve(读取没有报错,按 Latin-1 回退)
同一个文件里既有合法的 UTF-8 中文又有一个 0xE9(后面还有内容):b = 䏿,c = café(整个文件按 ISO-8859-1 重读,中文变成乱码)
0xE9 恰好是文件的最后一个字节:MissingResourceException: Can't find bundle for base name N, locale ;cause = java.nio.charset.MalformedInputException: Input length = 1
file.encoding / native.encoding / stdout.encoding = UTF-8 / UTF-8 / UTF-8;PropertyResourceBundle 读取不依赖它们
手工用 Reader 构造 PropertyResourceBundle(ISO_8859_1):café naïve
对照:java.util.Properties.load(InputStream) 读同一个 UTF-8 文件:name = 䏿 é ü ð(按 Latin-1 解码) [class]
同时有 Pri_en.class 与 Pri_en.properties 时 getBundle 得到 Pri_en,greet = hello-from-CLASS,onlyprops 存在吗? false
非字符串值:getObject(num) = 42(Integer);getStringArray(arr) = [a, b]
getString(num) → ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
clearCache 后 FORMAT_PROPERTIES 只找 properties:hello-from-PROPERTIES
clearCache 后 FORMAT_CLASS 只找 class:hello-from-CLASS [cache]
修改文件后再 getBundle:同一个对象? true;v = 1(缓存里仍是旧值)
clearCache(loader) 之后:同一个对象? false;v = 2
另一个类加载器(同一个目录)加载:同一个对象? false;v = 2
默认 Control.getTimeToLive = -2(TTL_NO_EXPIRATION_CONTROL = -2,TTL_DONT_CACHE = -1)
TTL=300 ms 的自定义 Control:立刻再取 v = 1
过了 400 ms(过期且 needsReload 为 true)再取 v = 2
缓存命中的 getBundle + getString:239 ns;只 getString:5 ns(重复一次 193 ns,自写计时,sink=true)