首页 > 解决方案 > 将 java 字符串转换为扩展 ascii 字符集(代码页 437)

问题描述

我必须使用来自 UTF-8 外部数据的一些字符串,并且必须在旧的扩展 ascii dos 字符集中创建一个文本文件(IBM437)。外部数据是来自不同国家/地区的更大的产品名称、单位和诸如此类的东西(每个地区 1 万个),所以我预计会有很多非 dos 字符进来。

例如尝试过String("ő").getBytes("Cp437"),但它只返回一个问号(#67)。

是否有现有的解决方案可以获得“最接近”的 dos 字符?

标签: javacharacter-encoding

解决方案


例如尝试了 String("ő").getBytes("Cp437") ,但它只返回一个问号(#67)。

可能是因为 getBytes 会将所有不可映射的字符替换为默认替换字符,即?Cp437。但是,有很多警告,所以让我们在继续之前先仔细检查所有内容:

new String("ő")

除非您的编译器和您的编辑器同意,否则这直接不起作用:

  • 您在代码编辑器中编写该文本(或粘贴它,或诸如此类)。
  • 您在编辑器中按 CMD+S 或其他命令来保存文件。
  • 编辑器现在将一袋字节写入磁盘,因为文件是字节而不是字符。要执行此任务,您的编辑器会应用字符集编码。假设它正在应用UTF-8. 在大多数编辑器中,您可以配置它和/或它在某处的状态栏中可见。
  • 编译器现在打开文件,这是一袋字节,但它要解析字符而不是字节。因此,它也应用字符集编码将这些字节转换回字符。
  • 如果它使用不同的编码,那么它ő就会变成一个 gobbledygook character

所以,从那里开始。你确定 javac 和你的编辑器是同步的吗?javac有一个--charset标志,如果你使用的是构建系统,它有一个设置。我强烈建议您将其全部设置为 UTF-8。或者,如果您只是不确定如何做这些事情,您可以完全避开项目符号并完全用 ASCII 字符编写源代码,这样,编码不太重要(您可以使用 UTF-8 编码,使用 Cp437 进行解码,并使用 iso-8859-1 进行编码,如果输入是全 ASCII,则不会有任何损坏):

> cat Test.java
class Test {
  public static void main(String[] args) {
    String x = new String("\u0151");
    System.out.println(x);
  }
}
> javac Test.java
> java Test
ő

现在运行上面的代码并仔细检查它是否打印ő,因为进程(如java.exe将它们的数据作为一袋字节输出,因此终端本身也在应用字符集转换。如果这些东西中的任何一个配置错误,您就会陷入困境,因为您的工具堆栈配置错误。让我们在继续之前确认它不是。

好的,我确认 javac AND editor 是 UTF_8,上面打印 ő

伟大的。让我们继续:

.getBytes("Cp437")

然后,这会生成一个包含您的字符串的字节数组,使用 Cp437 编码。

阅读文档是根本

让我们阅读 getBytes 方法的文档。

此方法始终使用此字符集的默认替换字节数组替换格式错误的输入和不可映射的字符序列。当需要对编码过程进行更多控制时,应使用 CharsetEncoder 类。

这听起来像是很好的建议,所以让我们放弃代码并按照文档的建议去做:

CharsetEncoder encoder = Charset.forName("Cp437")
  .newEncoder()
  .onMalformedInput(CodingErrorAction.REPORT)
  .onUnmappableCharacter(CodingErrorAction.REPORT)
  ;

CharBuffer cb = CharBuffer.wrap("\u0151");
ByteBuffer bb = encoder.encode(cb);

运行这个我们得到:

Exception in thread "main" java.nio.charset.UnmappableCharacterException: Input length = 1

这告诉我们那里有一个在 Cp437 中无法表示的角色。情况确实如此:字符 ő 不能在 Cp437 中表示

现在我们进入一个“诺贝尔奖”级别的复杂故事:你如何将一个角色音译成预期的更 ascii-ish 的等价物?

如果不了解输入语言,这是不可能的

这是一个简单的例子:

在德语中ö,如果您必须用 ascii 写字母,则将其转录为oe. 例如,如果某些网站不允许,“Schröder”会将其转录为“Schroeder”。(为了好玩:谷歌“schroeder”你会得到大约 4200 万个结果;“schroder”只会让你少一点)。

然而,在瑞典语中,这封信ö被转录为 o。例如,在名称“Henrik Samuel Conrad Sjögren”中,他们会将其转录为“Sjogren”。搜索 Sjogren 会发现比 Sjoegren 更多的命中 - 你在 Sjoegren 上获得的命中是失败的转录- 盲目认为 ascii-ize ö 的最佳方法是使其成为 oe 而不管上下文如何的自动化系统。

结论是,以人类的方式进行连接是人工智能级别的困难。

这就留下了仅仅摆脱重音的死记硬背策略,即使这在语言技术上不是正确的策略。换句话说,将“Schröder”转录为“Schroder”,即使说德语的人不会这样做。

那,你可以在代码中做到:

String o = "\u0151";
String v = Normalizer.normalize(o, Normalizer.Form.NFKD);
v = v.replaceAll("[^\\p{ASCII}]", "");
System.out.println(v);
> o

这将首先将该单个字符分解为 2 个字符:普通字符o,然后是一个特殊字符,表示:“在前一个字符上加上双锐角”(这就是 Normalizer 所做的 - 将事物拆分为一个简单的符号,然后是一个单独的字符这意味着:以某种方式修改前一个 - 这称为“分解”)。然后,我们使用正则表达式 replace all 来删除所有非 ascii,只留下基本的 ascii o。请注意,并非所有东西都像这样干净地分解 - 像“ß”这样的字符会变成虚无。您可能希望将其转录为 'ss' - 但同样,前提是您知道输入是德语或其他某种语言,其中 'ß' 应该转录为 ss,而不是保证。

这是 asciification,您要求“给我在 Cp437 中最接近的字母”。我假设你可能在想:嘿,Cp437 实际上在位置 148 包含一个 o-umlaut。但是,那里有一个问题:你的眼球可以判断它们看起来相似,但“看起来相似”并不是一个好的转录方式。这表明您可以将大写 i (I) 替换为小写 L (l),因为“它们看起来很相似”。

元音变音和双尖音符之间没有关系。除此之外,它们看起来很相似,但仅此而已。双锐音主要用于匈牙利语,元音变音主要用于德语和瑞典语。一个真正的枪和奶奶的情况:他们彼此无关。

因此,如果您的问题是:我想将 ő 转录为 ö,我认为不存在任何东西,您必须自己编写这样的工具。如果您打算这样做,我强烈建议您首先通过规范化器运行您的输入(以另一种方式规范化,将基本字母 + 修饰符字符组合成一个组合字符,然后编写一个字符映射列表,然后应用它。这个然后 list 将包括'ő'to 'ö'. 仅基于您认为应该这样做的内容。

如果您的问题是:我想将 ő 转录为 o,上面的代码就是这样做的。


推荐阅读