如何在 Java 中用 Unicode 字符填充字符串

How to pad Strings with Unicode characters in Java

我将右填充添加到字符串以将其输出为 table 格式。

for (String[] tuple : testData) {
  System.out.format("%-32s -> %s\n", tuple[0], tuple[1]);
}

结果如下(随机测试数据):

znZfmOEQ0Gb68taaNU6HY21lvo       -> Xq2aGqLedQnTSXg6wmBNDVb
frKweMCH8Kvgyk0J                 -> lHJ5r7YDV0jTL
NxtHP                            -> odvPJklwIzZZ
NX2scXjl5dxWmer                  -> wPDlKCKllVKk
x2HKsSHCqDQ                      -> RMuWLZ2vaP9sOF0yHmjVysJ
b0hryXKd6b80xAI                  -> 05MHjvTOxlxq1bvQ8RGe

当存在多字节 unicode 字符时,此方法不起作用:

0OZotivbyGhZM1FIwNhn6r6cC -> OKDxDV1o2NMqXH3VvE7q3uONwEcY5V
fBHRCjU4K8OCdzACmQZSn6WO         -> gvGBtUO5a4gPMKj9BKqBHFKx1iO7
cDUhb0cXkLWkS                -> SZX
WtP9t                            -> Q0wWOeY3W66mM5rcQQYKpG
va4du8SS                       -> KI
a71?⚖TZ‍♀ws5J              -> b8A

如您所见,对齐已关闭。

我的想法是计算字符串的长度与使用的字节数之间的差异,并使用它来抵消填充,如下所示:

int correction = tuple[0].getBytes().length - tuple[0].length();

然后我不会填充到 32 个字符,而是填充到 32 + correction。但是,这也没有用。

这是我的测试代码(使用 emoji-java 但该行为应该可以用任何 unicode 字符重现):

import java.util.Collection;
import org.apache.commons.lang3.RandomStringUtils;
import com.vdurmont.emoji.Emoji;
import com.vdurmont.emoji.EmojiManager;

public class Test {

  public static void main(String[] args) {
    // create random test data
    String[][] testData = new String[15][2];
    for (String[] tuple : testData) {
      tuple[0] = RandomStringUtils.randomAlphanumeric(2, 32);
      tuple[1] = RandomStringUtils.randomAlphanumeric(2, 32);
    }

    // add some emojis
    Collection<Emoji> all = EmojiManager.getAll();
    for (String[] tuple : testData) {
      for (int i = 1; i < tuple[0].length(); i++) {
        if (Math.random() > 0.90) {
          Emoji emoji = all.stream().skip((int) (all.size() * Math.random())).findFirst().get();
          tuple[0] = tuple[0].substring(0, i - 1) + emoji.getUnicode() + tuple[0].substring(i + 1);
        }
      }
    }

    // output
    for (String[] tuple : testData) {
      System.out.format("%-32s -> %s\n", tuple[0], tuple[1]);
    }
  }
}

正如@Xehpuk 链接到的问题中的评论所讨论的那样,在 this discussion on kotlinlang.org as well as in this blog post by Daniel Lemire 中,以下内容似乎是正确的:

The problem is that the java String class represents characters as UTF-16 characters. This means any unicode character that is represented by more than 16 bits is saved as 2 separate Char values. This fact is ignored by many of the functions within String, eg. String.lenght does not return the number of unicode characters, it returns the number of 16bit characters within the String, some emoji counting for 2 characters.

然而,该行为似乎是特定于实现的。

正如 David 在他的 post 中提到的那样,您可以尝试以下方法来获得正确的长度:

tuple.codePointCount(0, tuple.length())

请参阅 Java SE 文档中的 code point methods

这里实际上存在一些问题,除了一些字体显示的标志比其他字符宽。我假设您想将中国国旗算作一个字符(因为它在屏幕上被绘制为一个元素)。

字符串 class 报告的长度不正确

字符串 class 与 char 一起使用,它们是 Unicode 代码点的 16 位整数。问题是并非所有代码点都适合 16 位,只有来自基本多语言平面 (BMP) 的代码点适合那些 charStringlength() 方法 returns char 的数量,而不是代码点的数量。

现在 StringcodePointCount 方法在这种情况下可能会有所帮助:它计算给定索引范围内的代码点数。因此,提供 string.length() 作为方法的第二个参数 returns 代码点的总数。

组合字符

但是,还有一个问题。例如,中国国旗由 两个 Unicode 代码点 组成:区域指示符号字母 C (, U+1F1E8) 和 N (, U+1F1F3)。这两个代码点组合成中国国旗。这是您无法使用 codePointCount 方法解决的问题。

区域指标符号字母 seem 是一个特殊场合。其中两个字符可以组合成一面国旗。我不知道实现您想要的目标的标准方法。您可能需要手动考虑这一点。

我写了一个小程序来获取字符串的长度。

static int length(String str) {
    String a = "\uD83C\uDDE6";
    String z = "\uD83C\uDDFF";

    Pattern p = Pattern.compile("[" + a + "-" + z + "]{2}");
    Matcher m = p.matcher(str);
    int count = 0;
    while (m.find()) {
        count++;
    }
    return str.codePointCount(0, str.length()) - count;
}