2010-08-26 13 views
8

Creo lo siguiente para truncar una cadena en java a una nueva cadena con un número dado de bytes.Truncando cadenas por bytes

 String truncatedValue = ""; 
     String currentValue = string; 
     int pivotIndex = (int) Math.round(((double) string.length())/2); 
     while(!truncatedValue.equals(currentValue)){ 
      currentValue = string.substring(0,pivotIndex); 
      byte[] bytes = null; 
      bytes = currentValue.getBytes(encoding); 
      if(bytes==null){ 
       return string; 
      } 
      int byteLength = bytes.length; 
      int newIndex = (int) Math.round(((double) pivotIndex)/2); 
      if(byteLength > maxBytesLength){ 
       pivotIndex = newIndex; 
      } else if(byteLength < maxBytesLength){ 
       pivotIndex = pivotIndex + 1; 
      } else { 
       truncatedValue = currentValue; 
      } 
     } 
     return truncatedValue; 

Esto es lo primero que vino a mi mente, y sé que podría mejorar en él. Vi otra publicación que estaba haciendo una pregunta similar allí, pero estaban truncando cadenas usando los bytes en lugar de String.substring. Creo que preferiría usar String.substring en mi caso.

EDITAR: Acabo de eliminar la referencia UTF8 porque también podría hacer esto para diferentes tipos de almacenamiento.

+0

Me gustaría reformular su problema. Está intentando encajar una cadena en una matriz de bytes que no puede ser mayor que maxUTF8BytesLength. Desea usar UTF-8 para la codificación. Desea copiar la mayor cantidad de caracteres posible. ¿Correcto? – gawi

+0

correcto, yo diría que es correcto. También me gustaría hacerlo de manera eficiente. – stevebot

+0

Acabo de editar la pregunta para no hacer referencia a UTF-8. Lo siento, fue engañoso. – stevebot

Respuesta

11

¿Por qué no convertir a bytes y seguir adelante - obedeciendo los límites de caracteres UTF8 a medida que lo hace - hasta que tenga el número máximo, luego convierta esos bytes a una cadena?

O usted podría cortar la cadena original si se mantiene un registro de dónde debe producirse el corte:

// Assuming that Java will always produce valid UTF8 from a string, so no error checking! 
// (Is this always true, I wonder?) 
public class UTF8Cutter { 
    public static String cut(String s, int n) { 
    byte[] utf8 = s.getBytes(); 
    if (utf8.length < n) n = utf8.length; 
    int n16 = 0; 
    int advance = 1; 
    int i = 0; 
    while (i < n) { 
     advance = 1; 
     if ((utf8[i] & 0x80) == 0) i += 1; 
     else if ((utf8[i] & 0xE0) == 0xC0) i += 2; 
     else if ((utf8[i] & 0xF0) == 0xE0) i += 3; 
     else { i += 4; advance = 2; } 
     if (i <= n) n16 += advance; 
    } 
    return s.substring(0,n16); 
    } 
} 

Nota: editado para corregir errores en 2014-08-25

+1

Definitivamente podría hacer eso. ¿Hay alguna razón por la cual usar String.substring sea peor? Parece que hacerlo de la forma en que describes tendría que dar cuenta de todos los puntos de código, lo cual no es muy divertido. (dependiendo de tu definición de diversión :)). – stevebot

+0

@stevebot - Para ser eficiente, debe aprovechar la estructura conocida de los datos. Si no le importa la eficiencia y desea que sea fácil, o si desea admitir todas las codificaciones Java posibles sin tener que saber de qué se trata, su método parece lo suficientemente razonable. –

1

usted podría convierta la cadena a bytes y convierta esos bytes a una cadena.

public static String substring(String text, int maxBytes) { 
    StringBuilder ret = new StringBuilder(); 
    for(int i = 0;i < text.length(); i++) { 
     // works out how many bytes a character takes, 
     // and removes these from the total allowed. 
     if((maxBytes -= text.substring(i, i+1).getBytes().length) < 0) break; 
     ret.append(text.charAt(i)); 
    } 
    return ret.toString(); 
} 
+0

Comprobar que el carácter uno por uno puede no ser bueno para el rendimiento – NguyenDat

+2

@nguyendat, hay muchas razones por las que esto no es muy efectivo. La principal sería la creación de objeto para la subcadena() y getBytes() Sin embargo, te sorprendería lo mucho que puedes hacer en un milisegundo y eso suele ser suficiente. –

+1

Ese método no maneja adecuadamente los pares de sustitución, p. subcadena ("\ uD800 \ uDF30 \ uD800 \ uDF30", 4) .getBytes ("UTF-8"). La longitud devolverá 8, no 4. La mitad de un par suplente se representa como un solo byte "?" por String.getBytes ("UTF-8"). –

3

Uso del CharsetEncoder UTF-8, y codificar hasta que la salida ByteBuffer contiene tantos bytes como usted está dispuesto a tomar, mediante la búsqueda de CoderResult.OVERFLOW.

2

Como se ha señalado, Peter Lawrey tiene inconveniente mayor rendimiento (~ 3,500msc de 10.000 veces), Rex Kerr era mucho mejor (~ 500msc de 10.000 veces) pero el resultado no fue preciso: cortó mucho más de lo que necesitaba (en lugar de permanecer 4000 bytes, queda como 3500 para algunos ejemplos). Se adjunta aquí mi solución (~ 250msc de 10.000 veces) asumiendo que UTF-8 max longitud de carbonización en bytes es 4 (gracias Wikipedia):

public static String cutWord (String word, int dbLimit) throws UnsupportedEncodingException{ 
    double MAX_UTF8_CHAR_LENGTH = 4.0; 
    if(word.length()>dbLimit){ 
     word = word.substring(0, dbLimit); 
    } 
    if(word.length() > dbLimit/MAX_UTF8_CHAR_LENGTH){ 
     int residual=word.getBytes("UTF-8").length-dbLimit; 
     if(residual>0){ 
      int tempResidual = residual,start, end = word.length(); 
      while(tempResidual > 0){ 
       start = end-((int) Math.ceil((double)tempResidual/MAX_UTF8_CHAR_LENGTH)); 
       tempResidual = tempResidual - word.substring(start,end).getBytes("UTF-8").length; 
       end=start; 
      } 
      word = word.substring(0, end); 
     } 
    } 
    return word; 
} 
+0

¿No parece que esta solución impide un medio par suplente? En segundo lugar, en caso de que la longitud de getBytes() se aplicara a las dos mitades de un par suplente individualmente (no es inmediatamente obvio para mí, nunca lo hará), también subestimaría el tamaño de la representación UTF-8 del par como un todo, suponiendo que la "matriz de bytes de reemplazo" es un solo byte. En tercer lugar, los puntos de código UTF-8 de 4 bytes requieren un par suplente de dos caracteres en Java, por lo que el máximo es de 3 bytes por carácter Java. –

0

s = new String(s.getBytes("UTF-8"), 0, MAX_LENGTH - 2, "UTF-8");

5

creo que la solución de Rex Kerr tiene 2 errores.

  • Primero, truncará para limitar + 1 si un carácter no ASCII está justo antes del límite. Truncar "123456789á1" dará como resultado "123456789á" que se representa en 11 caracteres en UTF-8.
  • En segundo lugar, creo que malinterpretó el estándar UTF. https://en.wikipedia.org/wiki/UTF-8#Description muestra que un 110xxxxx al comienzo de una secuencia UTF nos dice que la representación es de 2 caracteres de longitud (en oposición a 3). Esa es la razón por la que su implementación usualmente no agota todo el espacio disponible (como notó Nissim Avitan).

Por favor, encontrar mi versión corregida a continuación:

public String cut(String s, int charLimit) throws UnsupportedEncodingException { 
    byte[] utf8 = s.getBytes("UTF-8"); 
    if (utf8.length <= charLimit) { 
     return s; 
    } 
    int n16 = 0; 
    boolean extraLong = false; 
    int i = 0; 
    while (i < charLimit) { 
     // Unicode characters above U+FFFF need 2 words in utf16 
     extraLong = ((utf8[i] & 0xF0) == 0xF0); 
     if ((utf8[i] & 0x80) == 0) { 
      i += 1; 
     } else { 
      int b = utf8[i]; 
      while ((b & 0x80) > 0) { 
       ++i; 
       b = b << 1; 
      } 
     } 
     if (i <= charLimit) { 
      n16 += (extraLong) ? 2 : 1; 
     } 
    } 
    return s.substring(0, n16); 
} 

todavía pensé que esto estaba lejos de ser efectiva.Así que si usted realmente no necesita la representación de cadena del resultado y la matriz de bytes va a hacer, puede utilizar esta:

private byte[] cutToBytes(String s, int charLimit) throws UnsupportedEncodingException { 
    byte[] utf8 = s.getBytes("UTF-8"); 
    if (utf8.length <= charLimit) { 
     return utf8; 
    } 
    if ((utf8[charLimit] & 0x80) == 0) { 
     // the limit doesn't cut an UTF-8 sequence 
     return Arrays.copyOf(utf8, charLimit); 
    } 
    int i = 0; 
    while ((utf8[charLimit-i-1] & 0x80) > 0 && (utf8[charLimit-i-1] & 0x40) == 0) { 
     ++i; 
    } 
    if ((utf8[charLimit-i-1] & 0x80) > 0) { 
     // we have to skip the starter UTF-8 byte 
     return Arrays.copyOf(utf8, charLimit-i-1); 
    } else { 
     // we passed all UTF-8 bytes 
     return Arrays.copyOf(utf8, charLimit-i); 
    } 
} 

Lo curioso es que, con un límite de byte 20-500 realista que realizan más o menos la el mismo IF crea nuevamente una cadena del conjunto de bytes.

Tenga en cuenta que ambos métodos suponen una entrada utf-8 válida que es una suposición válida después de usar la función getBytes() de Java.

+0

También debería detectar UnsupportedEncodingException en s.getBytes ("UTF-8") – asalamon74

+0

No veo que getBytes arroje nada. Aunque http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#getBytes%28java.lang.String%29 dice "El comportamiento de este método cuando esta cadena no se puede codificar en el juego de caracteres dado no está especificado ". –

+1

La página que ha vinculado muestra que arroja UnsupportedEncodingException: "byte público [] getBytes (String charsetName) throws UnsupportedEncodingException" – asalamon74

0

Ésta es mi:

private static final int FIELD_MAX = 2000; 
private static final Charset CHARSET = Charset.forName("UTF-8"); 

public String trancStatus(String status) { 

    if (status != null && (status.getBytes(CHARSET).length > FIELD_MAX)) { 
     int maxLength = FIELD_MAX; 

     int left = 0, right = status.length(); 
     int index = 0, bytes = 0, sizeNextChar = 0; 

     while (bytes != maxLength && (bytes > maxLength || (bytes + sizeNextChar < maxLength))) { 

      index = left + (right - left)/2; 

      bytes = status.substring(0, index).getBytes(CHARSET).length; 
      sizeNextChar = String.valueOf(status.charAt(index + 1)).getBytes(CHARSET).length; 

      if (bytes < maxLength) { 
       left = index - 1; 
      } else { 
       right = index + 1; 
      } 
     } 

     return status.substring(0, index); 

    } else { 
     return status; 
    } 
} 
0

Por mediante el siguiente expresión regular también puede eliminar delantero y trasero espacio en blanco del carácter de doble byte.

stringtoConvert = stringtoConvert.replaceAll("^[\\s ]*", "").replaceAll("[\\s ]*$", ""); 
0

Esto no se podía ser la solución más eficiente, pero funciona

public static String substring(String s, int byteLimit) { 
    if (s.getBytes().length <= byteLimit) { 
     return s; 
    } 

    int n = Math.min(byteLimit-1, s.length()-1); 
    do { 
     s = s.substring(0, n--); 
    } while (s.getBytes().length > byteLimit); 

    return s; 
} 
5

La solución más sensata es usar decodificador:

final Charset CHARSET = Charset.forName("UTF-8"); // or any other charset 
final byte[] bytes = inputString.getBytes(CHARSET); 
final CharsetDecoder decoder = CHARSET.newDecoder(); 
decoder.onMalformedInput(CodingErrorAction.IGNORE); 
decoder.reset(); 
final CharBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes, 0, limit)); 
final String outputString = decoded.toString(); 
0

He mejorado en la solución de Peter Lawrey a manejar con precisión los pares de sustitución. Además, I optimizado basado en el hecho de que el número máximo de bytes por char en codificación UTF-8 es 3.

public static String substring(String text, int maxBytes) { 
    for (int i = 0, len = text.length(); (len - i) * 3 > maxBytes;) { 
     int j = text.offsetByCodePoints(i, 1); 
     if ((maxBytes -= text.substring(i, j).getBytes(StandardCharsets.UTF_8).length) < 0) 
      return text.substring(0, i); 
     i = j; 
    } 
    return text; 
} 
Cuestiones relacionadas