2012-06-20 13 views
5

Czy istnieje sposób na utworzenie StringBuilder z byte[]?Utwórz StringBuilder z bajtu []

Chcę poprawić wykorzystanie pamięci przy użyciu StringBuilder ale co mam najpierw jest byte[], więc muszę stworzyć String z byte[] a następnie utworzyć StringBuilder od String i nie widzę tego jako rozwiązanie optymalne.

Dzięki

+6

Śmiem twierdzić, że nie jest to teoretycznie optymalne, ale czy w rzeczywistości jest to * problem *? Czy wykonałeś odpowiednie testy, aby pokazać, jaki ma to wpływ? –

+5

Co jest następną rzeczą, którą robisz z tym StringBuilderem? – Thilo

+2

Jeśli chcesz mieć większą kontrolę nad procesem dekodowania i jakiej pamięci używasz, możesz bezpośrednio użyć ['CharsetDecoder'] (http://docs.oracle.com/javase/7/docs/api/java/nio/charset /CharsetDecoder.html) i dekodować w dedykowanym 'CharBuffer' (zamiast używać' StringBuilder'). –

Odpowiedz

12

Zasadniczo, najlepszym rozwiązaniem wydaje się być za pomocą CharsetDecoder bezpośrednio.

Oto jak:

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

DODANO:

Po niektórych badań wydaje się, że proste new String(bytes) jest znacznie szybszy i wydaje się, nie ma prostego sposobu, aby go szybciej niż. Oto test Pobiegłem:

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

Wyniki:

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

I zmodyfikowaną (mniej pamięci czasochłonne) Wersja na IDEONE: Here.

+0

Nie wydaje się, aby zaoszczędzić pamięć w porównaniu do używania tylko String. Aby było to przydatne, musi nadal używać CharBuffer zamiast StringBuilder. – Thilo

+0

@Thilo, hm, pójdę sprawdzić źródła StringBuilder, ale możesz mieć rację. – bezmax

+0

@Thilo, wydaje się, że się mylisz. W przypadku użycia z tymczasową zmienną String (rozwiązanie OP w treści pytania), CharBuffer jest konwertowany na String, który jest następnie kopiowany do wewnętrznej tablicy char w StringBuilder. W moim rozwiązaniu CharBuffer jest kopiowany bezpośrednio do wewnętrznej tablicy char w StringBuilder, używając metod CharSequence. Dlatego nie potrzebujemy 1 tymczasowego obiektu typu String w pamięci. – bezmax

4

Jeśli potrzebne są krótkie instrukcje, wówczas nie ma sposobu na przejście między krokami String. Konstruktor String miksuje konwersję i konstruowanie obiektów dla wygody w bardzo częstym przypadku, ale nie ma takiego konstruktora wygody dla StringBuilder.

Jeśli jest wydajność jesteś zainteresowany, to możesz uniknąć pośredniego obiekt String za pomocą czegoś takiego:

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

Jeśli chcesz, aby móc dostroić wydajność, można kontrolować Odszyfruj samemu. Na przykład możesz nie używać zbyt dużej ilości pamięci, używając wartości averageCharsPerByte jako szacunkowej ilości pamięci potrzebnej. Zamiast zmiany rozmiaru bufora, jeśli oszacowanie było zbyt krótkie, można użyć wynikowego StringBuilder do gromadzenia wszystkich części.

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

Wątpię jednak, czy powyższy kod będzie wart wysiłku w większości aplikacji. Jeśli aplikacja jest zainteresowana wydajnością, prawdopodobnie nie powinna zajmować się również StringBuilder, ale obsługuje wszystko na poziomie bufora.

+0

"Konstruktor String łączy konwersję i budowę obiektu dla wygody w bardzo częstym przypadku": Nie tylko wygoda - konwersja i konstrukcja są faktycznie połączone, String bezpośrednio wykorzystuje wewnętrzną tablicę używaną przez konwerter, więc istnieje korzyść z wydajności, także. Myślę więc, że bardzo trudno byłoby go pokonać pod względem wydajności (twoje podejście wydaje się w najlepszym razie ekwiwalentne, zobacz także odpowiedź @ Maxa). – Thilo

+0

Mówienie o (StringCoding) [http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/lang/StringCoding.java] implementacji OpenJDK: tak, używa tej samej tablicy, jeśli spełnione są wszystkie następujące warunki: - Dekoder został dostarczony z JDK - Szacowany rozmiar był prawidłowy Dla np. UTF-8, jeśli na wejściu jest pojedyncza sekwencja wielobajtowa, konieczne będzie tutaj również skopiowanie tablicy. Co prawdopodobnie spowoduje równą wydajność, nie gorsze niż moje sugestie. – MvG