在Java中将UTF-8转换为ISO-8859-1

14

我正在读取一个XML文档(UTF-8编码),并最终将内容显示在使用ISO-8859-1编码的Web页面上。如预期,一些字符无法正确显示,例如(它们显示为?)。

是否可能将这些字符从UTF-8转换为ISO-8859-1?

这是我尝试编写的代码片段:

BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();

String line = null;
while ((line = br.readLine()) != null) {
  sb.append(line);
}
br.close();

byte[] latin1 = sb.toString().getBytes("ISO-8859-1");

return new String(latin1);

我不太确定出了什么问题,但我相信是readLine()导致的麻烦(因为字符串会被Java/UTF-16编码?)。我尝试的另一种变化是将latin1替换为

byte[] latin1 = new String(sb.toString().getBytes("UTF-8")).getBytes("ISO-8859-1");

我已经阅读了关于这个主题的以前的帖子,而我正在学习中。提前感谢您的帮助。

4个回答

16

我不确定标准库中是否有一个规范化程序可以做到这一点。我认为“智能”引号的转换不是由标准Unicode normalizer例程处理的 - 但不要引用我。

明智的做法是放弃ISO-8859-1,开始使用UTF-8。尽管如此,仍然可以将任何通常允许的Unicode代码点编码为以ISO-8859-1编码的HTML页面。您可以使用escape sequences进行编码,如此处所示:

public final class HtmlEncoder {
  private HtmlEncoder() {}

  public static <T extends Appendable> T escapeNonLatin(CharSequence sequence,
      T out) throws java.io.IOException {
    for (int i = 0; i < sequence.length(); i++) {
      char ch = sequence.charAt(i);
      if (Character.UnicodeBlock.of(ch) == Character.UnicodeBlock.BASIC_LATIN) {
        out.append(ch);
      } else {
        int codepoint = Character.codePointAt(sequence, i);
        // handle supplementary range chars
        i += Character.charCount(codepoint) - 1;
        // emit entity
        out.append("&#x");
        out.append(Integer.toHexString(codepoint));
        out.append(";");
      }
    }
    return out;
  }
}

使用示例:

String foo = "This is Cyrillic Ya: \u044F\n"
    + "This is fraktur G: \uD835\uDD0A\n" + "This is a smart quote: \u201C";

StringBuilder sb = HtmlEncoder.escapeNonLatin(foo, new StringBuilder());
System.out.println(sb.toString());

上面的字符“左双引号”(U+201C)被编码为&#x201C;。其他几个任意代码点也被编码。
使用这种方法需要注意。如果您的文本需要转义为HTML,则需要在上述代码之前进行转义,否则会导致“&”被转义。

运行得很好,谢谢! - Chocula
这刚刚为我省了很多麻烦! - daniel0mullins
请看一下我在@robinst答案下的评论。 - heroin

4

根据您的默认编码,以下行可能会导致问题:

byte[] latin1 = sb.toString().getBytes("ISO-8859-1");

return new String(latin1);

在Java中,String/Char始终为UTF-16BE编码。只有当您将字符转换为字节时才涉及不同的编码。如果您的默认编码为UTF-8,则将latin1缓冲区视为UTF-8,并且某些Latin-1序列可能会形成无效的UTF-8序列,这时您将获得?。


3

在Java 8中,可以将McDowell的答案简化为以下形式(同时保留代理对的正确处理):

public final class HtmlEncoder {
    private HtmlEncoder() {
    }

    public static <T extends Appendable> T escapeNonLatin(CharSequence sequence,
                                                          T out) throws java.io.IOException {
        for (PrimitiveIterator.OfInt iterator = sequence.codePoints().iterator(); iterator.hasNext(); ) {
            int codePoint = iterator.nextInt();
            if (Character.UnicodeBlock.of(codePoint) == Character.UnicodeBlock.BASIC_LATIN) {
                out.append((char) codePoint);
            } else {
                out.append("&#x");
                out.append(Integer.toHexString(codePoint));
                out.append(";");
            }
        }
        return out;
    }
}

我不确定这是否是正确的 Latin-1(ISO-8859-1)符号检查。我使用以下检查代替:Charset.forName("ISO-8859-1").newEncoder().canEncode((char) codePoint), 当然,Charset.forName("ISO-8859-1").newEncoder() 应该保持为常量或方法变量。 - heroin

1
当您实例化String对象时,需要指定要使用的编码方式。
因此,请替换:
return new String(latin1);

return new String(latin1, "ISO-8859-1");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接