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

5
我将字符串的右侧填充以使其符合表格格式的输出。
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个字符。但是,这也没有起作用。

以下是我的测试代码(使用 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]);
    }
  }
}

这个回答解决了你的问题吗?如何在Java中正确计算字符串的长度? - xehpuk
谢谢,这提供了一些背景信息,但我仍然无法解决我的具体问题。 - martin
2个回答

2

这里实际上有几个问题,除了一些字体显示的国旗比其他字符宽之外。我假设您想将中国国旗视为一个单独的字符(因为它在屏幕上绘制为一个单独的元素)。

String类报告错误的长度

String类使用char类型,这是Unicode代码点的16位整数。问题在于,并非所有代码点都适合16位,只有从基本多语言平面(BMP)中的代码点适合那些char类型。String的length()方法返回char的数量,而不是代码点的数量。

现在String的codePointCount方法可以在这种情况下帮助:它计算给定索引范围内的代码点数。因此,将string.length()作为第二个参数提供给该方法会返回代码点的总数。

组合字符

然而,还有另一个问题。例如,中国国旗由两个Unicode代码点组成:区域指示符号字母C(U+1F1E8)和N(U+1F1F3)。这两个代码点被合并成为中国国旗。使用codePointCount方法无法解决这个问题。
区域指示符号字母似乎是一个特殊情况。两个这样的字符可以组合成一个国旗。我不知道有一种标准的方法来实现你想要的功能。你可能需要手动考虑这个问题。
我写了一个小程序来获取字符串的长度。
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;
}

1
正如@Xehpuk链接的问题中的评论、kotlinlang.org中的讨论以及Daniel Lemire的博客文章所讨论的那样,以下内容似乎是正确的:
问题在于Java String类将字符表示为UTF-16字符。这意味着任何由超过16位表示的Unicode字符都将保存为2个单独的Char值。这个事实被String内的许多函数忽略了,例如String.lenght不返回Unicode字符的数量,而是返回字符串中16位字符的数量,有些表情符号计为2个字符。
但是,行为似乎是特定于实现的。
正如David在他的文章中提到的,您可以尝试以下方法来获取正确的长度:
tuple.codePointCount(0, tuple.length())

请查看Java SE文档中的编码点方法


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