2012-06-20 11 views
5

¿Hay alguna manera de crear un StringBuilder desde un byte[]?Crear StringBuilder desde el byte []

quiero mejorar el uso de memoria usando StringBuilder pero lo que tengo es una primera byte[], así que tengo que crear un String del byte[] y luego crear el StringBuilder del String y yo no veo esto como solución óptima.

Gracias

+6

Me atrevo a decir que no es teóricamente óptimo, ¿pero en realidad es un * problema *? ¿Has hecho las pruebas adecuadas para demostrar el impacto que esto está teniendo? –

+5

¿Qué es lo siguiente que estás haciendo con ese StringBuilder? – Thilo

+2

Si desea tener más control sobre el proceso de decodificación y qué memoria usa, puede usar directamente un ['CharsetDecoder'] (http://docs.oracle.com/javase/7/docs/api/java/nio/charset) /CharsetDecoder.html) y decodificar en un 'CharBuffer' dedicado (en lugar de usar un' StringBuilder'). –

Respuesta

12

Básicamente, su mejor opción parece estar usando CharsetDecoder directamente.

Así es como:

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

añadido:

Después de algunas pruebas, parece que el simple new String(bytes) es mucho más rápido y parece que no hay una forma sencilla de hacerlo más rápido que eso. Aquí está la prueba me encontré:

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

Resultados:

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

y A (menos memoria que consume) versión modificada por Ideone: Here.

+0

Eso no parece ahorrar memoria en comparación con solo usar una Cadena. Para que esto sea útil, debe seguir usando el CharBuffer en lugar de un StringBuilder. – Thilo

+0

@Thilo, hm, vamos a revisar las fuentes de StringBuilder, pero podría estar en lo cierto. – bezmax

+0

@Thilo, parece que estás equivocado. Si se utiliza con una variable de cadena temporal (solución OP en el cuerpo de la pregunta), CharBuffer se convierte en cadena, que luego se copia en matriz de caracteres interna en StringBuilder. En mi solución, CharBuffer se copia directamente en la matriz de caracteres interna en StringBuilder, utilizando los métodos CharSequence. Por lo tanto, no necesitamos 1 objeto String temporal en la memoria. – bezmax

4

Si son enunciados cortos los que desea, no hay forma de evitar el paso de String en el medio. El constructor String mezcla la conversión y la construcción de objetos para mayor comodidad en un caso muy común, pero no existe un constructor de conveniencia para StringBuilder.

Si se trata de rendimiento le interesa, entonces es posible que evite el objeto de cadena intermedia mediante el uso de algo como esto:

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

Si usted quiere ser capaz de ajustar el rendimiento, se puede controlar el decodificar el proceso usted mismo. Por ejemplo, es posible que desee evitar el uso de demasiada memoria, utilizando averageCharsPerByte como una estimación de la cantidad de memoria que se necesitará. En lugar de cambiar el tamaño del búfer si ese cálculo era demasiado corto, podría usar el StringBuilder resultante para acumular todas las partes.

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

Sin embargo, dudo que el código anterior valga la pena en la mayoría de las aplicaciones. Si una aplicación está interesada en el rendimiento, probablemente no debería tratar con StringBuilder tampoco, pero maneja todo a nivel del búfer.

+0

"El constructor String mezcla la conversión y la construcción de objetos para mayor comodidad en un caso muy común": no solo la conveniencia: la conversión y la construcción están vinculadas, el String usa directamente el arreglo interno utilizado por el convertidor, por lo que hay un beneficio de rendimiento. también. Así que creo que sería muy difícil superarlo en el rendimiento (su enfoque parece ser, en el mejor de los casos, equivalente, véase también la respuesta de @ Max). – Thilo

+0

Hablando sobre la implementación (StringCoding) [http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/lang/StringCoding.java] de OpenJDK: sí, usa la misma matriz, si se cumplen todas las condiciones siguientes: - El decodificador se envió con el JDK - El tamaño estimado fue correcto Por ejemplo UTF-8, si hay una sola secuencia multibyte en la entrada, también será necesaria una copia de matriz aquí. Lo cual probablemente dará como resultado un rendimiento igual, no peor que mis sugerencias. – MvG

Cuestiones relacionadas