web-dev-qa-db-fra.com

Implémenter une fonction pour vérifier si un tableau de chaînes/octets suit le format utf-8

J'essaie de résoudre cette question d'entrevue. 

Après avoir clairement défini le format UTF-8. ex: 1 octet: 0b0xxxxxxx 2- octets: .... Invité à écrire une fonction pour valider si l'entrée est valide UTF-8. L'entrée sera un tableau de chaînes/octets, la sortie Devrait être oui/non.

J'ai deux approches possibles. 

Tout d’abord, si l’entrée est une chaîne, UTF-8 étant au maximum de 4 octets, une fois les deux premiers caractères "0b" supprimés, nous pouvons utiliser Integer.parseInt (s) pour vérifier si le reste de la chaîne se trouve bien au début. gamme de 0 à 10FFFF. De plus, il est préférable de vérifier si la longueur de la chaîne est un multiple de 8 et si la chaîne en entrée contient tous les 0 et les 1 en premier. Je vais donc devoir parcourir la chaîne deux fois et la complexité sera O (n). 

Deuxièmement, si l'entrée est un tableau d'octets (nous pouvons également utiliser cette méthode si l'entrée est une chaîne), nous vérifions si chaque élément d'un octet est dans la plage correcte. Si l'entrée est une chaîne, vérifiez d'abord que la longueur de la chaîne est un multiple de 8, puis vérifiez que chaque sous-chaîne de 8 caractères se trouve dans la plage. 

Je sais qu'il existe plusieurs solutions pour vérifier une chaîne à l'aide de bibliothèques Java, mais ma question est de savoir comment implémenter la fonction en fonction de la question. 

Merci beaucoup. 

12
DoraShine

Eh bien, je suis reconnaissant pour les commentaires et la réponse. Tout d’abord, je dois admettre que c’est «une autre question d’entrevue stupide». Il est vrai que Java est déjà codé dans String, il sera donc toujours compatible avec UTF-8. Une façon de vérifier est donné une chaîne: 

public static boolean isUTF8(String s){
    try{
        byte[]bytes = s.getBytes("UTF-8");
    }catch(UnsupportedEncodingException e){
        e.printStackTrace();
        System.exit(-1);
    }
    return true;
}

Cependant, comme toutes les chaînes imprimables sont au format Unicode, je n’ai donc pas eu la chance d’obtenir une erreur. 

Deuxièmement, si un tableau d'octets est donné, il sera toujours compris entre -2 ^ 7 (0b10000000) et 2 ^ 7 (0b1111111), de sorte qu'il sera toujours dans une plage valide UTF-8. 

Ma compréhension initiale de la question était que, étant donné une chaîne, dites "0b11111111", vérifiez si c'est un UTF-8 valide, je suppose que je me suis trompé. 

De plus, Java fournit le constructeur pour convertir le tableau d'octets en chaîne, et si vous êtes intéressé par la méthode de décodage, vérifiez ici

Une dernière chose, la réponse ci-dessus serait correcte étant donné une autre langue. La seule amélioration pourrait être: 

En novembre 2003, la RFC 3629 limitait UTF-8 à U + 10FFFF afin de respecter les contraintes du codage de caractères UTF-16. Cela a supprimé toutes les séquences de 5 et 6 octets et environ la moitié des séquences de 4 octets.

Donc 4 octets suffiraient. 

Je suis vraiment à cela, alors corrigez-moi si je me trompe. Merci beaucoup. 

0
DoraShine

Jetons d'abord un coup d'œil à une représentation visuelle du design de l'UTF-8 .

enter image description here


Maintenant, reprenons ce que nous devons faire.

  • Boucle sur tous les caractères de la chaîne (chaque caractère étant un octet).
  • Nous devrons appliquer un masque à chaque octet en fonction du point de code, car les caractères x représentent le point de code réel. Nous allons utiliser l'opérateur AND AND (&) binaire, qui copie un bit dans le résultat s'il existe dans les deux opérandes.
  • Le but de l'application d'un masque est de supprimer les bits de fin afin que nous comparions l'octet réel en tant que premier point de code. Nous allons effectuer l'opération au niveau des bits en utilisant 0b1xxxxxxx où 1 apparaîtra "Octets en séquence" heure et les autres bits seront 0.
  • Nous pouvons ensuite comparer avec le premier octet pour vérifier s'il est valide et également déterminer quel est l'octet réel.
  • Si le caractère entré dans aucun cas, cela signifie que l'octet est invalide et nous retournons "Non".
  • Si nous pouvons sortir de la boucle, cela signifie que chaque caractère est valide, donc la chaîne est valide.
  • Assurez-vous que la comparaison renvoyée par true correspond à la longueur attendue.

La méthode ressemblerait à ceci:

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;
}

Edit: La méthode actuelle n'est pas la méthode originale (presque, mais pas) et est volée de ici . L'original ne fonctionnait pas correctement selon le commentaire de @EJP. 

12

Une petite solution pour la vérification de la compatibilité UTF-8 dans le monde réel:

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);
}

Vous pouvez vérifier les résultats des tests:

@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();
}

Copie des cas de test de https://codereview.stackexchange.com/questions/59428/validating-utf-8-byte-array

3
Thiago Mata
public static boolean validUTF8(byte[] input) {
    int i = 0;
    // Check for BOM
    if (input.length >= 3 && (input[0] & 0xFF) == 0xEF
            && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) {
        i = 3;
    }

    int end;
    for (int j = input.length; i < j; ++i) {
        int octet = input[i];
        if ((octet & 0x80) == 0) {
            continue; // ASCII
        }

        // Check for UTF-8 leading byte
        if ((octet & 0xE0) == 0xC0) {
            end = i + 1;
        } else if ((octet & 0xF0) == 0xE0) {
            end = i + 2;
        } else if ((octet & 0xF8) == 0xF0) {
            end = i + 3;
        } else {
            // Java only supports BMP so 3 is max
            return false;
        }

        while (i < end) {
            i++;
            octet = input[i];
            if ((octet & 0xC0) != 0x80) {
                // Not a valid trailing byte
                return false;
            }
        }
    }
    return true;
}
1
Koray Tugay