Comment tronquer une chaîne java pour tenir dans un nombre d'octets donné, une fois encodé en UTF-8?

StackOverflow https://stackoverflow.com/questions/119328

Question

Comment tronquer un String java afin de savoir qu'il tiendra dans un nombre donné d'octets de stockage une fois codé en UTF-8?

Était-ce utile?

La solution

Voici une boucle simple qui comptabilise la taille de la représentation UTF-8 et la tronque lorsqu'elle est dépassée:

public static String truncateWhenUTF8(String s, int maxBytes) {
    int b = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);

        // ranges from http://en.wikipedia.org/wiki/UTF-8
        int skip = 0;
        int more;
        if (c <= 0x007f) {
            more = 1;
        }
        else if (c <= 0x07FF) {
            more = 2;
        } else if (c <= 0xd7ff) {
            more = 3;
        } else if (c <= 0xDFFF) {
            // surrogate area, consume next char as well
            more = 4;
            skip = 1;
        } else {
            more = 3;
        }

        if (b + more > maxBytes) {
            return s.substring(0, i);
        }
        b += more;
        i += skip;
    }
    return s;
}

Ceci traite les paires de substitution qui apparaissent dans la liste chaîne d'entrée. Le codeur UTF-8 de Java génère (correctement) les paires de substitution sous forme d'une séquence unique de 4 octets au lieu de deux séquences de 3 octets. truncateWhenUTF8 () renverra la chaîne tronquée la plus longue possible. Si vous ignorez les paires de substitution dans l'implémentation, les chaînes tronquées risquent d'être raccourcies plus que nécessaire.

Je n'ai pas beaucoup testé ce code, mais voici quelques tests préliminaires:

private static void test(String s, int maxBytes, int expectedBytes) {
    String result = truncateWhenUTF8(s, maxBytes);
    byte[] utf8 = result.getBytes(Charset.forName("UTF-8"));
    if (utf8.length > maxBytes) {
        System.out.println("BAD: our truncation of " + s + " was too big");
    }
    if (utf8.length != expectedBytes) {
        System.out.println("BAD: expected " + expectedBytes + " got " + utf8.length);
    }
    System.out.println(s + " truncated to " + result);
}

public static void main(String[] args) {
    test("abcd", 0, 0);
    test("abcd", 1, 1);
    test("abcd", 2, 2);
    test("abcd", 3, 3);
    test("abcd", 4, 4);
    test("abcd", 5, 4);

    test("a\u0080b", 0, 0);
    test("a\u0080b", 1, 1);
    test("a\u0080b", 2, 1);
    test("a\u0080b", 3, 3);
    test("a\u0080b", 4, 4);
    test("a\u0080b", 5, 4);

    test("a\u0800b", 0, 0);
    test("a\u0800b", 1, 1);
    test("a\u0800b", 2, 1);
    test("a\u0800b", 3, 1);
    test("a\u0800b", 4, 4);
    test("a\u0800b", 5, 5);
    test("a\u0800b", 6, 5);

    // surrogate pairs
    test("\uD834\uDD1E", 0, 0);
    test("\uD834\uDD1E", 1, 0);
    test("\uD834\uDD1E", 2, 0);
    test("\uD834\uDD1E", 3, 0);
    test("\uD834\uDD1E", 4, 4);
    test("\uD834\uDD1E", 5, 4);

}

Mis à jour Exemple de code modifié, il gère désormais les paires de substitution.

Autres conseils

Vous devez utiliser CharsetEncoder , le simple getBytes () + copier autant que vous le pouvez peut couper les caractères UTF-8 en deux.

Quelque chose comme ça:

public static int truncateUtf8(String input, byte[] output) {

    ByteBuffer outBuf = ByteBuffer.wrap(output);
    CharBuffer inBuf = CharBuffer.wrap(input.toCharArray());

    Charset utf8 = Charset.forName("UTF-8");
    utf8.newEncoder().encode(inBuf, outBuf, true);
    System.out.println("encoded " + inBuf.position() + " chars of " + input.length() + ", result: " + outBuf.position() + " bytes");
    return outBuf.position();
}

Voici ce que j'ai proposé: il utilise des API Java standard, il devrait donc être sûr et compatible avec toutes les paires unicode bizarre et de substitution, etc. La solution provient de http://www.jroller.com/holy/entry/truncating_utf_string_to_the avec des vérifications ajoutées pour éviter le décodage lorsque la chaîne contient moins d'octets que maxBytes .

/**
 * Truncates a string to the number of characters that fit in X bytes avoiding multi byte characters being cut in
 * half at the cut off point. Also handles surrogate pairs where 2 characters in the string is actually one literal
 * character.
 *
 * Based on: http://www.jroller.com/holy/entry/truncating_utf_string_to_the
 */
public static String truncateToFitUtf8ByteLength(String s, int maxBytes) {
    if (s == null) {
        return null;
    }
    Charset charset = Charset.forName("UTF-8");
    CharsetDecoder decoder = charset.newDecoder();
    byte[] sba = s.getBytes(charset);
    if (sba.length <= maxBytes) {
        return s;
    }
    // Ensure truncation by having byte buffer = maxBytes
    ByteBuffer bb = ByteBuffer.wrap(sba, 0, maxBytes);
    CharBuffer cb = CharBuffer.allocate(maxBytes);
    // Ignore an incomplete character
    decoder.onMalformedInput(CodingErrorAction.IGNORE)
    decoder.decode(bb, cb, true);
    decoder.flush(cb);
    return new String(cb.array(), 0, cb.position());
}

Le codage UTF-8 a un trait soigné qui vous permet de voir où vous vous trouvez dans un jeu d'octets.

Vérifiez le flux à la limite de caractères souhaitée.

  • Si son bit de poids fort est 0, il s'agit d'un caractère codé sur un octet, remplacez-le simplement par 0 et tout va bien.
  • Si son bit haut est 1 et qu'il en est de même pour le bit suivant, vous êtes au début d'un caractère multi-octets. Réglez donc cet octet sur 0 et vous êtes bon.
  • Si le bit fort est 1 mais que le bit suivant est 0, alors vous êtes au milieu d'un caractère, revenez dans la mémoire tampon jusqu'à atteindre un octet contenant au moins deux 1 dans les bits hauts et remplacez cet octet avec 0.

Exemple: si votre flux est: 31 33 31 C1 A3 32 33 00, vous pouvez définir une chaîne de 1, 2, 3, 5, 6 ou 7 octets, mais pas 4, car cela mettrait le 0 après C1, qui est le début d’un caractère multi-octets.

vous pouvez utiliser -new String (data.getBytes ("UTF-8"), 0, maxLen, "UTF-8");

Vous pouvez calculer le nombre d'octets sans effectuer de conversion.

foreach character in the Java string
  if 0 <= character <= 0x7f
     count += 1
  else if 0x80 <= character <= 0x7ff
     count += 2
  else if 0x800 <= character <= 0xd7ff // excluding the surrogate area
     count += 3
  else if 0xdc00 <= character <= 0xffff
     count += 3
  else { // surrogate, a bit more complicated
     count += 4
     skip one extra character in the input stream
  }

Vous devez détecter des paires de substitution (D800-DBFF et U + DC00 & # 8211; U + DFFF) et compter 4 octets pour chaque paire de substitution valide. Si vous obtenez la première valeur dans la première plage et la seconde dans la deuxième plage, tout va bien, ignorez-les et ajoutez 4. Mais sinon, c'est une paire de substitution invalide. Je ne sais pas comment Java s'en occupe, mais votre algorithme devra compter correctement dans ce cas (peu probable).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top