Comment puis-je tronquer une chaîne de caractères java pour s'adapter à un nombre donné d'octets, une fois encodé UTF-8?
Comment puis-je tronquer un java String
de sorte que je sais qu'il s'adaptera dans un nombre donné d'octets de stockage une fois qu'il est encodé UTF-8?
5 réponses
Voici une boucle simple qui compte quelle sera la taille de la représentation UTF-8 et qui se 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;
}
Ce ne poignée les paires de substitution qui apparaissent dans la chaîne d'entrée. L'encodeur UTF-8 de Java (correctement) produit des paires de substitution comme une seule séquence de 4 octets au lieu de deux séquences de 3 octets, donc truncateWhenUTF8()
retournera la plus longue chaîne tronquée qu'il puisse. Si vous ignorez les couples de substitution dans l'implémentation alors les chaînes tronquées peuvent être court-circuitées qu'elles devaient être.
je n'ai pas fait beaucoup de tests sur le 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);
}
mise à jour exemple de code modifié, il traite maintenant des paires de substituts.
vous devez utiliser CharsetEncoder , le simple getBytes()
+ copier autant que vous pouvez couper les charcteurs 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 inventé, il utilise des API Java standard donc devrait être sûr et compatible avec toutes les bizarreries unicode et les paires de substituts, etc. La solution est tirée de http://www.jroller.com/holy/entry/truncating_utf_string_to_the avec contrôles ajoutés pour null et pour éviter le décodage lorsque la chaîne est 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());
}
encodage UTF-8 a un trait soigné qui vous permet de voir où dans un byte-set Vous êtes.
vérifiez le flux à la limite de caractères que vous voulez.
- si son high bit est 0, c'est un char à un seul octet, il suffit de le remplacer par 0 et vous êtes très bien.
- si son high bit est 1 et que c'est le prochain bit, alors vous êtes au début d'un char multi-octets, donc il suffit de mettre ce byte à 0 et vous êtes bon.
- si le bit de haut est 1 mais le bit suivant est 0, alors vous êtes au milieu d'un personnage, voyagez le long de la mémoire tampon jusqu'à ce que vous frappez un octet qui a 2 ou plus 1s dans les bits de haut, et remplacez ce octet par 0.
exemple: si votre flux est: 31 33 31 C1 A3 32 33 00, Vous pouvez faire votre chaîne 1, 2, 3, 5, 6, ou 7 octets de long, mais pas 4, comme cela placerait le 0 après C1, qui est le début d'un char multi-octets.
Vous pouvez calculer le nombre d'octets, sans faire 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 devriez détecter des paires de substituts (D800-DBFF et U+DC00–U+DFFF) et compter 4 octets pour chaque paire de substituts valide. Si vous obtenez la première valeur de la première plage et le deuxième dans la deuxième plage, tout est ok, passez-les et ajoutez-4. Mais si non, alors c'est une défaillance de la paire de substitution. Je ne sais pas comment Java gère ça, mais votre algorithme devra faire le bon choix. comptant à ce que (peu probable).