【问题标题】:How to get the first symbol of a string如何获取字符串的第一个符号
【发布时间】:2018-06-04 00:43:59
【问题描述】:

我需要显示字符串的第一个符号。最简单的代码是:

String text = "test string";
char firstSymbol = text[0];

但是如果字符不适合 16 位,例如 "\uD83D\uDC68" (????, U+1F468),这将不起作用。只返回一半字符,并呈现为问号。

String text = "test string";
int codePoint = text.codePointAt(0);
char[] chars = Character.toChars(codePoint);
String firstSymbol = new String(chars);

这适用于以 Unicode 表示的任何字符。但是,也有 Unicode 字符序列显示为一个符号。当我为它们运行上面的代码时,只显示部分符号,就像 "\uD83D\uDC68\u200D\uD83D\uDCBB" (????‍????) 发生的那样。在这种情况下,我希望结果是整个字符串。我该如何处理这种情况?


当然应该是 charAt(),我的错。但是char 是 UTF-16 编码的,不能包含多个字符。第一个例子应该是这样的:

String text = "test string";
char firstSymbol = text.charAt(0);

另一个符号的例子是"\u0D23\u0D4D\u200D" (ൺ)。它的末尾有两个字符和零宽度的连接符。


我尝试使用 android.icu 库,它继承自 ICU4J,但不幸的是,它仅从 API 24 开始受支持。此外,它产生与第二个示例相同的结果,即它不连接字符,如果零宽度连接器在它们之间。

int breakIterator = BreakIterator.getCharacterInstance();
breakIterator.setText(text);
int begin = breakIterator.first();
int end = breakIterator.next();
String firstSymbol = text.substring(begin, end);

【问题讨论】:

  • "但是如果字符不适合 16 位,这不起作用" -> 它不起作用,因为语法 (char firstSymobl = text[0];) 错误。我认为您的意思是使用 text.charAt(0) (这不会解决您的“超过 16 位”问题)
  • 你可以使用 text.substring(0,1) 它会给你字符串的第一个字符。但是在这样做之前,请检查字符串不会为空。
  • @Lucky 它会返回大小为 1 的 String,而不是 char :) 从 String 获取字符的方法是 charAt(int index)
  • 已修复,请查看更新后的问题。我还假设字符串不为空也不为空

标签: java android string unicode


【解决方案1】:

\u200D 是 Unicode 代码点 U+200D ZERO WIDTH JOINER。如果要提取连接的代码点序列,则必须手动迭代字符串,直到遇到未连接的代码点,例如:

String text = ...;
StringBuilder sequence = new StringBuilder(text.length());
boolean isInJoin = false;
int codePoint;

for (int i = 0; i < text.length(); i = text.offsetByCodePoints(i, 1))
{
    codePoint = text.codePointAt(i);

    if (codePoint == 0x200D)
    {
        isInJoin = true;
        if (sequence.length() == 0)
            continue;
    }
    else
    {
        if ((sequence.length() > 0) && (!isInJoin)) break;
        isInJoin = false;
    }

    sequence.appendCodePoint(codePoint);
}

if (isInJoin)
{
    for(int i = sequence.length()-1; i >= 0; --i)
    {
        if (sequence.charAt(i) == 0x200D)
            sequence.deleteCharAt(i);
        else
            break;
    }
}

String firstSymbols = sequence.toString();

或者:

String text = ...;
boolean isInJoin = false;
int start = 0, length = 0, next;
int codePoint;

for (int i = 0; i < text.length(); i = next)
{
    codePoint = text.codePointAt(i);

    if (codePoint == 0x200D)
    {
        isInJoin = true;
        if (length == 0)
        {
            next = text.offsetByCodePoints(i, 1);
            start = next;
            continue;
        }
    }
    else
    {
        if ((length > 0) && (!isInJoin)) break;
        isInJoin = false;
    }

    next = text.offsetByCodePoints(i, 1);
    length += (next - i);
}

if (isInJoin)
{
    for(int i = length-1; i >= 0; --i)
    {
        if (text.charAt(i) == 0x200D)
            --length;
        else
            break;
    }
}

String firstSymbols = text.substring(start, start+length);

【讨论】:

    【解决方案2】:

    我注意到你使用Zero-width joiner(\u200D)字符加入表情符号,你可以通过这个字符split你的String:

    String[] result = text.split("\u200D");
    

    并通过以下方式获取第一个字符:

    String firstSymbol = result[0];
    

    【讨论】:

    • 感谢您提及零宽度连接器,我将尝试了解更多有关此的信息。但是,我需要获取两个加入的表情符号才能正确显示结果。如果它们被零宽度连接符分隔,我将尝试迭代字符串并加入表情符号。
    • 当你用零宽度的连接符分割String时,你会得到字符串数组,你可以遍历它们来一个一个地得到所有的表情符号。
    • 我已经实现了,但是效果不好,因为出现了太多的极端情况。请查看更新后的问题。
    猜你喜欢
    • 2011-03-26
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-21
    • 2017-07-27
    相关资源
    最近更新 更多