Como eu truncar uma string java para caber em um determinado número de bytes, uma vez UTF-8 codificado?

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

Pergunta

Como eu truncar um java String modo que eu sei que vai se encaixar em um determinado número de bytes de armazenamento, uma vez que é UTF-8 codificado?

Foi útil?

Solução

Aqui está um loop simples que conta o quão grande a representação UTF-8 vai ser, e trunca quando é excedida:

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 faz substitutos pares que aparecem no string de entrada. UTF-8 codificador de Java (correctamente) gera pares de substituição como uma única sequência de 4 bytes em vez de duas sequências de três bytes, de modo truncateWhenUTF8() voltará a cadeia mais longa truncado que pode. Se você ignorar pares substitutos na implementação, em seguida, as cordas truncado pode estar em curto do que precisava ser.

Eu não fiz um monte de testes em que o código, mas aqui estão alguns testes 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);

}

Atualização exemplo de código modificado, ele agora lida com pares substitutos.

Outras dicas

Você deve usar CharsetEncoder , o getBytes() simples + copiar como muitos como você pode pode cortar UTF-8 charcters ao meio.

Algo parecido com isto:

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

Aqui está o que eu vim com, ele usa padrão Java APIs por isso deve ser seguro e compatível com todos os estranheza unicode e pares substitutos etc. A solução é tirado de http://www.jroller.com/holy/entry/truncating_utf_string_to_the com controlos adicionados para nulo e para evitar a descodificar quando a cadeia é menos bytes do 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());
}

Codificação UTF-8 tem uma característica pura que permite que você veja onde em um byte-set você é.

verificar o fluxo no limite de caracteres que você deseja.

  • Se seu alto bit é 0, é um char de byte único, apenas substituí-lo com 0 e você está bem.
  • Se seu alto bit é 1 e assim é o próximo bit, então você está no início de um char multi-byte, então basta definir que byte a 0 e você é bom.
  • Se o bit é 1, mas o próximo bit é 0, então você está no meio de um personagem, viagem de volta ao longo do tampão até atingir um byte que tem 2 ou mais 1s nos bits altos, e substituir que byte com 0.

Exemplo: Se o seu fluxo é: 31 33 31 C1 A3 32 33 00, você pode fazer sua seqüência 1, 2, 3, 5, 6 ou 7 bytes de comprimento, mas não 4, como que iria colocar a 0 depois C1, que é o início de um carácter de multi-byte.

Você pode usar cordas -novo (data.getBytes ( "UTF-8"), 0, maxLen, "UTF-8");

Você pode calcular o número de bytes sem fazer qualquer conversão.

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
  }

Você teria que detectar pares substitutos (D800-DBFF e U + DC00-U + DFFF) e contar 4 bytes para cada par substituto válido. Se você receber o primeiro valor no primeiro intervalo eo segundo na segunda gama, está tudo ok, ignorá-los e adicionar 4. Mas se não, então é um par substituto inválido. Eu não sei como Java lida com isso, mas o seu algoritmo terá que fazer a contagem direita nesse caso (improvável).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top