2010-08-26 10 views
8

Creo quanto segue per il troncamento di una stringa in java in una nuova stringa con un determinato numero di byte.Troncamento di stringhe per byte

 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; 

Questa è la prima cosa che mi è venuta in mente e so che potrei migliorare. Ho visto un altro post che stava facendo una domanda simile lì, ma stavano troncando le stringhe usando i byte invece di String.substring. Penso che preferirei usare String.substring nel mio caso.

MODIFICA: Ho appena rimosso il riferimento UTF8 perché preferirei farlo anche per diversi tipi di archiviazione.

+0

avrei riformulare il problema. Stai tentando di inserire una stringa in un array di byte che non può essere più grande di maxUTF8BytesLength. Vuoi usare UTF-8 per la codifica. Vuoi copiare il maggior numero di caratteri possibile. Corretta? – gawi

+0

a destra, direi che è corretto. Mi piacerebbe anche farlo in modo efficiente. – stevebot

+0

Ho appena modificato la domanda per non fare riferimento a UTF-8. Mi dispiace, era fuorviante. – stevebot

risposta

11

Perché non convertire in byte e andare avanti - obbedendo ai limiti di caratteri UTF8 mentre lo si fa - finché non si ottiene il numero massimo, quindi riconvertire quei byte in una stringa?

Oppure si può semplicemente tagliare la stringa originale se si tiene traccia di dove dovrebbe avvenire il taglio:

// 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: modificato per correggere i bug su 2014-08-25

+1

Potrei sicuramente farlo. C'è qualche ragione per cui l'uso di String.substring è peggio? Sembra che farlo nel modo in cui descrivi avrebbe dovuto tenere conto di tutti i punti di codice, il che non è molto divertente. (dipende dalla tua definizione di divertimento :)). – stevebot

+0

@stevebot - Per essere efficienti, è necessario sfruttare la struttura nota dei dati. Se non ti interessa l'efficienza e vuoi che sia facile, o se vuoi supportare ogni possibile codifica Java senza dover sapere di cosa si tratta, il tuo metodo sembra abbastanza ragionevole. –

1

si potrebbe converti la stringa in byte e converti solo quei byte in una stringa.

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

Controllare uno ad uno carattere potrebbe non essere buono per le prestazioni – NguyenDat

+2

@nguyendat, ci sono molte ragioni per cui questo non è molto performante. Il principale sarebbe la creazione dell'oggetto per la sottostringa() e getBytes() Tuttavia, si sarebbe sorpresi di quanto si possa fare in un milli-secondo e di solito è sufficiente. –

+1

Questo metodo non gestisce correttamente le coppie di surrogati, ad es. sottostringa ("\ uD800 \ uDF30 \ uD800 \ uDF30", 4) .getBytes ("UTF-8"). la lunghezza restituirà 8, non 4. La metà di una coppia surrogata viene rappresentata come un "byte singolo"? da String.getBytes ("UTF-8"). –

3

Utilizzare UTF-8 CharsetEncoder, e codificare fino a quando l'uscita ByteBuffer contiene il maggior numero di byte che si è disposti a prendere, con la ricerca di CoderResult.OVERFLOW.

2

Come osservato, soluzione Peter Lawrey ha grande svantaggio delle prestazioni (~ 3,500msc per 10.000 volte), Rex Kerr era molto meglio (~ 500msc per 10.000 volte), ma il risultato non è stato accurato - ha tagliato molto più del necessario (invece di rimanere 4000 byte ne resta 3500 per alcuni esempi). attaccato qui la mia soluzione (~ 250msc per 10.000 volte) assumendo che UTF-8 lunghezza massima char in byte è 4 (grazie 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

Non sembra che questa soluzione prevenga una coppia di surrogati della metà finale? In secondo luogo, nel caso in cui getBytes(). Lunghezza dovesse essere applicata a entrambe le metà di una coppia surrogata individualmente (non immediatamente ovvio per me non lo farà mai), sottovaluterebbe anche la dimensione della rappresentazione UTF-8 della coppia nel suo insieme, supponendo che la "matrice di byte sostitutiva" sia un singolo byte. In terzo luogo, i punti di codice UTF-8 a 4 byte richiedono tutti una coppia di surrogati di due caratteri in Java, quindi il valore massimo è di soli 3 byte per carattere Java. –

0

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

5

penso che la soluzione di Rex Kerr ha 2 bug.

  • Innanzitutto, troncherà per limitare + 1 se un carattere non ASCII è appena prima del limite. Troncando "123456789á1" si otterrà "123456789á" che è rappresentato in 11 caratteri in UTF-8.
  • In secondo luogo, penso che abbia interpretato erroneamente lo standard UTF. https://en.wikipedia.org/wiki/UTF-8#Description mostra che un 110xxxxx all'inizio di una sequenza UTF ci dice che la rappresentazione è lunga 2 caratteri (rispetto a 3). Questa è la ragione per cui la sua implementazione di solito non consuma tutto lo spazio disponibile (come notato da Nissim Avitan).

Si prega di trovare il mio versione corretta di seguito:

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

ho ancora pensato che questo era tutt'altro che efficace.Quindi, se non si ha realmente bisogno la rappresentazione String del risultato e l'array di byte farà, è possibile utilizzare questo:

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

La cosa divertente è che con un limite di byte 20-500 realistica eseguono praticamente la same IF si crea nuovamente una stringa dall'array di byte.

Si noti che entrambi i metodi presuppongono un input utf-8 valido che è un'ipotesi valida dopo l'utilizzo della funzione getBytes() di Java.

+0

Si dovrebbe anche prendere UnsupportedEncodingException su s.getBytes ("UTF-8") – asalamon74

+0

Non vedo getBytes che lancia nulla. Sebbene http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#getBytes%28java.lang.String%29 dice "Il comportamento di questo metodo quando questa stringa non può essere codificata nel set di caratteri specificato non è specificato. " –

+1

La pagina collegata mostra che UnsupportEncodingException genera: "public byte [] getBytes (String charsetName) genera UnsupportedEncodingException" – asalamon74

0

Questo è il mio:

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

Utilizzando sotto espressione regolare anche è possibile rimuovere iniziali e finali spazio bianco di carattere a doppio byte.

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

Questo non potrebbe essere la soluzione più efficiente, ma funziona

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 soluzione più sano sta usando decoder:

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

ho migliorato la soluzione di Peter Lawrey a gestire con precisione le coppie surrogate. Inoltre, ho ottimizzato in base al fatto che il numero massimo di byte per char UTF-8 codifica è 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; 
} 
Problemi correlati