如何读取非BMP(星际)Unicode补充字符(代码点)

8
G-Clef(U+1D11E)不属于基本多语言平面(BMP),这意味着它需要超过16位。几乎所有的Java读取函数都只返回一个char或一个同时包含仅16位int。哪个函数可以读取包括SMP、SIP、TIP、SSP和PUA在内的完整Unicode符号? 更新 我想知道如何从输入流中读取单个Unicode符号(或码点)。我既没有任何整数数组,也不想读取一行。
使用Character.toCodePoint()可以构建代码点,但此函数需要一个char。另一方面,读取char是不可能的,因为read()返回一个int。到目前为止,我最好的解决方法是这样的,但它仍然包含不安全的转换:
public int read_code_point (Reader input) throws java.io.IOException
{
  int ch16 = input.read();
  if (Character.isHighSurrogate((char)ch16))
    return Character.toCodePoint((char)ch16, (char)input.read());
  else 
    return (int)ch16;
}

我该如何做得更好? 更新2 另一个版本返回一个字符串,但仍然使用强制类型转换:
public String readchar (Reader input) throws java.io.IOException
{
  int i16 = input.read(); // UTF-16 as int
  if (i16 == -1) return null;
  char c16 = (char)i16; // UTF-16
  if (Character.isHighSurrogate(c16)) {
    int low_i16 = input.read(); // low surrogate UTF-16 as int
    if (low_i16 == -1)
      throw new java.io.IOException ("Can not read low surrogate");
    char low_c16 = (char)low_i16;
    int codepoint = Character.toCodePoint(c16, low_c16);
    return new String (Character.toChars(codepoint));
  }
  else 
    return Character.toString(c16);
}

剩下的问题是:铸件是否安全,或如何避免它们?

在标题中不需要添加主要标签。 - Andrew Thompson
3
可能是Java读取带有补充Unicode字符的字符流的重复问题。 - Jukka K. Korpela
2
可能的重复问题并没有包含答案。 - ceving
1
你的两个答案都是“正确”的(尽管第一个没有处理流的结尾)。你的类型转换没有任何不安全的地方。 - jtahlborn
阅读 WTF-8编码,将可能格式不正确的 UTF-16 解码为代码点,反之亦然... - JosefZ
2个回答

2
迄今为止,我最好的工作是这个,但它仍然包含不安全的转换。你提供的代码中唯一不安全的地方是,如果输入已经到达EOF,那么ch16可能是-1。如果您首先检查此条件,则可以保证其他(char)转换是安全的,因为Reader.read()被指定返回-1或在char范围内的值(0-0xFFFF)。
public int read_code_point (Reader input) throws java.io.IOException
{
  int ch16 = input.read();
  if (ch16 < 0 || !Character.isHighSurrogate((char)ch16))
    return ch16;
  else {
    int loSurr = input.read();
    if(loSurr < 0 || !Character.isLowSurrogate((char)loSurr)) 
      return ch16; // or possibly throw an exception
    else 
      return Character.toCodePoint((char)ch16, (char)loSurr);
  }
}

这还不是理想的情况,实际上您需要处理边缘情况,即第一个读取的字符是高代理项,但第二个不是匹配的低代理项,在这种情况下,您可能希望将第一个字符原样返回,并备份读取器,以便下一次读取给您下一个字符。但这仅在input.markSupported() == true的情况下有效。如果您可以保证这一点,那么怎么样?
public int read_code_point (Reader input) throws java.io.IOException
{
  int firstChar = input.read();
  if (firstChar < 0 || !Character.isHighSurrogate((char)firstChar)) {
    return firstChar;
  } else {
    input.mark(1);
    int secondChar = input.read();
    if(secondChar < 0) {
      // reached EOF
      return firstChar;
    } else if(!Character.isLowSurrogate((char)secondChar)) {
      // unpaired surrogates, un-read the second char
      input.reset();
      return firstChar;
    }
    else {
      return Character.toCodePoint((char)firstChar, (char)secondChar);
    }
  }
}

或者您可以将原始读取器包装在一个PushbackReader中,并使用unread(secondChar)

将其转换为代码点有什么好处?如果您想要执行任何有用的操作,最好将数据存储在字符串中。 - jtahlborn
@jtahlborn每个解析器都需要下一个字符而不是下一个字符串。你会说解析器没有用吗? - ceving

-1

完整的Unicode可以用UTF-8和UTF-16表示,通过字节序列或字节对(“Java字符”)。从字符串中可以提取完整的Unicode 代码点

int[] codePoints = { 0x1d11e };
String s = new String(codePoints, 0, codePoints.length);

for (int i = 0; i < s.length(); ) {
    int cp = s.codePointAt(i);
    i += Character.charCount(cp);
}

对于基本拉丁字符的文件,UTF-8 看起来很好。

以下代码可以读取完整的标准 Unicode 文件(使用 UTF-8 编码):

try (BufferedReader in = new BufferedReader(
        new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
    for (;;) {
        String line = in.readLine();
        if (line == null) {
            break;
        }
        ... do some thing with a Unicode line ...
    }
} catch (FileNotFoundException e) {
    System.err.println("No file: " + file.getPath());
} catch (IOException e) {
    ...
}

一个提供一个或多个Unicode代码的Java字符串的函数:
String s = unicodeToString(0x1d11e);
String s = unicodeToString(0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x1d11e);

public static String unicodeToString(int... codepoints) {
    return new String(codePoints, 0, codePoints.length);
}

更详细地说明一下;在这里,我从一个文件中读取,使用FileInputStream。也许混淆的是Unicode本身不是一种格式,而是符号的标准编号。UTF-8、UTF-16LE、UTF-16BE、UTF-16才是实际的二进制格式。实际上,Java使用两种Unicode格式:虽然char是UTF-16,但在.class字符串常量中存储为UTF-8。UTF-8覆盖了完整的Unicode。在上面的代码中,数组codePoints使用Unicode数字。 - Joop Eggen
1
问题要求输入单个符号而不是整行。使用 readline 会导致必须将其余部分标记为未读取。 - ceving
啊哈,我会将它加入到答案中。 - Joop Eggen

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