¿Cómo truncar una java de cadena para caber en un número determinado de bytes, una vez codificado en UTF-8?

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

Pregunta

¿Cómo truncar una java String por lo que sé que va a encajar en un determinado número de bytes de almacenamiento una vez que está codificado en UTF-8?

¿Fue útil?

Solución

Aquí es un simple bucle que cuenta lo grande que es el UTF-8 es la representación va a ser, y trunca cuando se supera:

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

Este ¿ manejar los pares suplentes que aparecen en la cadena de entrada.Java UTF-8 encoder (correctamente) salidas de los pares suplentes como una sola secuencia de 4 bytes en lugar de dos 3-secuencias de bytes, por lo que truncateWhenUTF8() volverá el más largo de la cadena truncada puede.Si usted ignora los pares suplentes en la aplicación, a continuación, de la interrupción en las cadenas puede ser en corto de lo necesario.

No he hecho un montón de pruebas en ese código, pero aquí están algunas pruebas preliminares:

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

}

Actualizado Modificado el código de ejemplo, que ahora se ocupa de los pares suplentes.

Otros consejos

Usted debe utilizar CharsetEncoder, el simple getBytes() + copia como muchos como usted puede cortar UTF-8 charcters en la mitad.

Algo como esto:

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

He aquí lo que se me ocurrió, se utiliza el estándar de la Api Java así que debe ser seguro y compatible con todas las unicode rareza y los pares suplentes etc.La solución es tomado de http://www.jroller.com/holy/entry/truncating_utf_string_to_the con los cheques añadido null y para evitar la decodificación cuando la cadena es menos bytes 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());
}

La codificación UTF-8 tiene una cuidada rasgo que permite ver en un byte que ustedes son.

compruebe la corriente en el límite de caracteres que desee.

  • Si su alto bit es 0, se trata de un solo byte, char, simplemente reemplazarlo con 0 y estás bien.
  • Si su bit más alto es 1, y así es el siguiente bit, entonces usted está en el inicio de un multi-byte char, por lo que sólo conjunto de bytes a 0 y en lo que eres bueno.
  • Si el bit es 1, pero el siguiente bit es 0, entonces usted está en el medio de un personaje, viajar a lo largo del búfer hasta que llegues a un byte que tiene 2 o más de 1s en la alta bits, y reemplazar con 0 bytes.

Ejemplo:Si el flujo es:31 33 31 C1 A3 32 33 00, usted puede hacer que su cadena de 1, 2, 3, 5, 6, o 7 bytes de largo, pero no de 4, como que iba a poner el 0 después de C1, que es el inicio de un multi-byte char.

usted puede usar-new String( de datos.getBytes("UTF-8") , 0, maxLen, "UTF-8");

Se puede calcular el número de bytes sin hacer ningún tipo de conversión.

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
  }

Usted tendría que detectar los pares suplentes (D800-u + dbff y U+dc00 no–U+u + dfff) y el recuento de 4 bytes para cada válido par suplente.Si usted obtiene el primer valor en el primer rango y el segundo en el segundo rango, todo ok, saltear y agregar 4.Pero si no, entonces no es válida par suplente.No estoy seguro de cómo Java ofertas con eso, pero su algoritmo tendrá que hacer la derecha contando en que la (improbable) caso.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top