2012-06-20 20 views
5

C'è un modo per creare un StringBuilder da un byte[]?Crea StringBuilder dal byte []

voglio migliorare l'utilizzo della memoria utilizzando StringBuilder ma quello che ho prima è una byte[], quindi devo creare un String dal byte[] e quindi creare la StringBuilder dal String e non vedo come questa soluzione ottimale.

Grazie

+6

Oserei dire che non è teoricamente ottimale, ma in realtà è un * problema *? Hai fatto test appropriati per mostrare quale impatto ha questo? –

+5

Qual è la prossima cosa che stai facendo con quel StringBuilder? – Thilo

+2

Se si desidera un maggiore controllo sul processo di decodifica e sulla memoria utilizzata, è possibile utilizzare direttamente un ['CharsetDecoder'] (http://docs.oracle.com/javase/7/docs/api/java/nio/charset /CharsetDecoder.html) e decodificare in un 'CharBuffer' dedicato (invece di usare' StringBuilder'). –

risposta

12

In sostanza, la soluzione migliore sembra essere utilizzando CharsetDecoder direttamente.

Ecco come:

byte[] srcBytes = getYourSrcBytes(); 

//Whatever charset your bytes are endoded in 
Charset charset = Charset.forName("UTF-8"); 
CharsetDecoder decoder = charset.newDecoder(); 

//ByteBuffer.wrap simply wraps the byte array, it does not allocate new memory for it 
ByteBuffer srcBuffer = ByteBuffer.wrap(srcBytes); 
//Now, we decode our srcBuffer into a new CharBuffer (yes, new memory allocated here, no can do) 
CharBuffer resBuffer = decoder.decode(srcBuffer); 

//CharBuffer implements CharSequence interface, which StringBuilder fully support in it's methods 
StringBuilder yourStringBuilder = new StringBuilder(resBuffer); 

aggiunto:

Dopo alcuni test sembra che il semplice new String(bytes) è molto più veloce e sembra che non v'è alcun modo semplice per renderlo più veloce di quello. Ecco il test mi sono imbattuto:

import java.io.IOException; 
import java.io.UnsupportedEncodingException; 
import java.nio.ByteBuffer; 
import java.nio.CharBuffer; 
import java.nio.charset.CharacterCodingException; 
import java.nio.charset.Charset; 
import java.nio.charset.CharsetDecoder; 
import java.text.ParseException; 

public class ConsoleMain { 
    public static void main(String[] args) throws IOException, ParseException { 
     StringBuilder sb1 = new StringBuilder("abcdefghijklmnopqrstuvwxyz"); 
     for (int i=0;i<19;i++) { 
      sb1.append(sb1); 
     } 
     System.out.println("Size of buffer: "+sb1.length()); 
     byte[] src = sb1.toString().getBytes("UTF-8"); 
     StringBuilder res; 

     long startTime = System.currentTimeMillis(); 
     res = testStringConvert(src); 
     System.out.println("Conversion using String time (msec): "+(System.currentTimeMillis()-startTime)); 
     if (!res.toString().equals(sb1.toString())) { 
      System.err.println("Conversion error"); 
     } 

     startTime = System.currentTimeMillis(); 
     res = testCBConvert(src); 
     System.out.println("Conversion using CharBuffer time (msec): "+(System.currentTimeMillis()-startTime)); 
     if (!res.toString().equals(sb1.toString())) { 
      System.err.println("Conversion error"); 
     } 
    } 

    private static StringBuilder testStringConvert(byte[] src) throws UnsupportedEncodingException { 
     String s = new String(src, "UTF-8"); 
     StringBuilder b = new StringBuilder(s); 
     return b; 
    } 

    private static StringBuilder testCBConvert(byte[] src) throws CharacterCodingException { 
     Charset charset = Charset.forName("UTF-8"); 
     CharsetDecoder decoder = charset.newDecoder(); 
     ByteBuffer srcBuffer = ByteBuffer.wrap(src); 
     CharBuffer resBuffer = decoder.decode(srcBuffer); 
     StringBuilder b = new StringBuilder(resBuffer); 
     return b; 
    } 
} 

Risultati:

Size of buffer: 13631488 
Conversion using String time (msec): 91 
Conversion using CharBuffer time (msec): 252 

e una versione modificata (meno memoria che consumano) su Ideone: Here.

+0

Questo non sembra risparmiare memoria rispetto al solo utilizzo di una stringa. Per renderlo utile, deve continuare a usare CharBuffer invece di StringBuilder. – Thilo

+0

@Thilo, hm, sta per andare a controllare i sorgenti di StringBuilder, ma potresti avere ragione. – bezmax

+0

@Thilo, sembra che tu abbia torto. Se utilizzato con una variabile String temporanea (soluzione OP nel corpo della domanda), CharBuffer viene convertito in String, che viene quindi copiato in un array di caratteri interno in StringBuilder. Nella mia soluzione, CharBuffer viene copiato direttamente nel char array interno in StringBuilder, usando i metodi CharSequence. Pertanto, non abbiamo bisogno di 1 oggetto String temporaneo in memoria. – bezmax

4

Se si tratta di istruzioni brevi desiderate, non vi è alcun modo per aggirare il passaggio Stringa. Il costruttore di stringhe mescola la conversione e la costruzione di oggetti per convenienza in un caso molto comune, ma non esiste un costruttore di tale convenienza per un oggetto StringBuilder.

Se si tratta di prestazioni che ti interessa, allora si potrebbe evitare l'oggetto String intermedio utilizzando qualcosa di simile:

new StringBuilder(Charset.forName(charsetName).decode(ByteBuffer.wrap(inBytes))) 

Se si vuole essere in grado di mettere a punto le prestazioni, è possibile controllare il decodifica te stesso. Ad esempio, potresti voler evitare di usare troppa memoria, usando averageCharsPerByte come stima di quanta memoria sarà necessaria. Invece di ridimensionare il buffer se tale stima era troppo breve, è possibile utilizzare lo StringBuilder risultante per accumulare tutte le parti.

CharsetDecoder cd = Charset.forName(charsetName).newDecoder(); 
cd.onMalformedInput(CodingErrorAction.REPLACE); 
cd.onUnmappableCharacter(CodingErrorAction.REPLACE); 
int lengthEstimate = Math.ceil(cd.averageCharsPerByte()*inBytes.length) + 1; 
ByteBuffer inBuf = ByteBuffer.wrap(inBytes); 
CharBuffer outBuf = CharBuffer.allocate(lengthEstimate); 
StringBuilder out = new StringBuilder(lengthEstimate); 
CoderResult cr; 
while (true) { 
    cr = cd.decode(inBuf, outBuf, true); 
    out.append(outBuf); 
    outBuf.clear(); 
    if (cr.isUnderflow()) break; 
    if (!cr.isOverflow()) cr.throwException(); 
} 
cr = cd.flush(outBuf); 
if (!cr.isUnderflow()) cr.throwException(); 
out.append(outBuf); 

Dubito che il codice sopra varrà la pena nella maggior parte delle applicazioni, però. Se un'applicazione è interessata alle prestazioni, probabilmente non dovrebbe occuparsi di StringBuilder, ma gestirà tutto a livello di buffer.

+0

"Il costruttore di stringhe mischia conversione e costruzione di oggetti per convenienza in un caso molto comune": non solo comodità: la conversione e la costruzione sono effettivamente collegate, la stringa utilizza direttamente l'array interno utilizzato dal convertitore, quindi c'è un vantaggio in termini di prestazioni, pure. Quindi penso che sarebbe molto difficile batterlo in modo performante (il tuo approccio sembra essere al massimo equivalente, vedi anche la risposta di @ Max). – Thilo

+0

Parlando di (StringCoding) [http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/lang/StringCoding.java] implementazione di OpenJDK: sì, utilizza lo stesso array, se tutte le seguenti condizioni sono valide: - Il decodificatore è stato spedito con il JDK - La stima della dimensione era corretta Per es. UTF-8, se c'è una singola sequenza multibyte nell'input, sarà necessaria anche una copia dell'array. Il che probabilmente porterà a risultati uguali, non peggiori dei miei suggerimenti. – MvG