实现一个函数以检查字符串/字节数组是否遵循 utf-8 格式

2022-09-03 13:53:15

我试图解决这个面试问题。

在给出明确的UTF-8格式定义之后。例如: 1 字节 : 0b0xxxxxxx 2- 字节:....要求编写函数以验证输入是否有效的 UTF-8。输入将是字符串/字节数组,输出应为是/否。

我有两种可能的方法。

首先,如果输入是字符串,由于UTF-8最多是4个字节,在我们删除前两个字符“0b”之后,我们可以使用Integer.parseInt(s)来检查字符串的其余部分是否在0到10FFFF的范围内。此外,最好检查字符串的长度是否是8的倍数,以及输入字符串是否首先包含所有0和1。因此,我将不得不遍历字符串两次,复杂性将为O(n)。

其次,如果输入是一个字节数组(如果输入是字符串,我们也可以使用此方法),我们检查每个1字节元素是否在正确的范围内。如果输入是字符串,请首先检查字符串的长度是 8 的倍数,然后检查每个 8 个字符的子字符串是否在范围内。

我知道有几个解决方案可以使用Java库检查字符串,但我的问题是我应该如何根据问题实现函数。

多谢。


答案 1

让我们首先看一下 UTF-8 设计的可视化表示形式。

enter image description here


现在让我们继续我们必须做的事情。

  • 循环访问字符串的所有字符(每个字符都是一个字节)。
  • 我们需要根据代码点对每个字节应用掩码,因为字符表示实际的代码点。我们将使用二进制 AND 运算符 (),如果它同时存在于两个操作数中,则该运算符会将位复制到结果中。x&
  • 应用掩码的目的是删除尾随位,以便我们将实际字节作为第一个码位进行比较。我们将使用其中1将出现“按顺序排列的字节”时间进行按位运算,而其他位将为0。0b1xxxxxxx
  • 然后,我们可以与第一个字节进行比较,以验证它是否有效,并确定什么是实际字节。
  • 如果输入的字符不是任何一种情况,则表示字节无效,我们返回“No”。
  • 如果我们能脱离循环,这意味着每个字符都是有效的,因此字符串是有效的。
  • 确保返回 true 的比较对应于预期的长度。

该方法将如下所示:

public static final boolean isUTF8(final byte[] pText) {

    int expectedLength = 0;

    for (int i = 0; i < pText.length; i++) {
        if ((pText[i] & 0b10000000) == 0b00000000) {
            expectedLength = 1;
        } else if ((pText[i] & 0b11100000) == 0b11000000) {
            expectedLength = 2;
        } else if ((pText[i] & 0b11110000) == 0b11100000) {
            expectedLength = 3;
        } else if ((pText[i] & 0b11111000) == 0b11110000) {
            expectedLength = 4;
        } else if ((pText[i] & 0b11111100) == 0b11111000) {
            expectedLength = 5;
        } else if ((pText[i] & 0b11111110) == 0b11111100) {
            expectedLength = 6;
        } else {
            return false;
        }

        while (--expectedLength > 0) {
            if (++i >= pText.length) {
                return false;
            }
            if ((pText[i] & 0b11000000) != 0b10000000) {
                return false;
            }
        }
    }

    return true;
}

编辑:实际的方法不是原始方法(几乎,但不是),并且是从这里偷来的。原来的那个没有按照@EJP评论正常工作。


答案 2

用于实际 UTF-8 兼容性检查的小型解决方案:

public static final boolean isUTF8(final byte[] inputBytes) {
    final String converted = new String(inputBytes, StandardCharsets.UTF_8);
    final byte[] outputBytes = converted.getBytes(StandardCharsets.UTF_8);
    return Arrays.equals(inputBytes, outputBytes);
}

您可以检查测试结果:

@Test
public void testEnconding() {

    byte[] invalidUTF8Bytes1 = new byte[]{(byte)0b10001111, (byte)0b10111111 };
    byte[] invalidUTF8Bytes2 = new byte[]{(byte)0b10101010, (byte)0b00111111 };
    byte[] validUTF8Bytes1 = new byte[]{(byte)0b11001111, (byte)0b10111111 };
    byte[] validUTF8Bytes2 = new byte[]{(byte)0b11101111, (byte)0b10101010, (byte)0b10111111 };

    assertThat(isUTF8(invalidUTF8Bytes1)).isFalse();
    assertThat(isUTF8(invalidUTF8Bytes2)).isFalse();
    assertThat(isUTF8(validUTF8Bytes1)).isTrue();
    assertThat(isUTF8(validUTF8Bytes2)).isTrue();
    assertThat(isUTF8("\u24b6".getBytes(StandardCharsets.UTF_8))).isTrue();
}

https://codereview.stackexchange.com/questions/59428/validating-utf-8-byte-array 复制测试用例