Java: Konvertieren von Zeichenfolgen in und von ByteBuffer und damit verbundene Probleme

81

Ich verwende Java NIO für meine Socket-Verbindungen und mein Protokoll basiert auf Text. Daher muss ich in der Lage sein, Strings in ByteBuffers zu konvertieren, bevor ich sie in den SocketChannel schreibe, und die eingehenden ByteBuffers wieder in Strings konvertieren. Derzeit verwende ich diesen Code:

public static Charset charset = Charset.forName("UTF-8");
public static CharsetEncoder encoder = charset.newEncoder();
public static CharsetDecoder decoder = charset.newDecoder();

public static ByteBuffer str_to_bb(String msg){
  try{
    return encoder.encode(CharBuffer.wrap(msg));
  }catch(Exception e){e.printStackTrace();}
  return null;
}

public static String bb_to_str(ByteBuffer buffer){
  String data = "";
  try{
    int old_position = buffer.position();
    data = decoder.decode(buffer).toString();
    // reset buffer's position to its original so it is not altered:
    buffer.position(old_position);  
  }catch (Exception e){
    e.printStackTrace();
    return "";
  }
  return data;
}

Dies funktioniert die meiste Zeit, aber ich frage mich, ob dies der bevorzugte (oder einfachste) Weg ist, um jede Richtung dieser Konvertierung durchzuführen, oder ob es einen anderen Weg gibt, es zu versuchen. Gelegentlich und scheinbar zufällig, um Anrufe encode()und decode()eine werfen java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_ENDAusnahme oder ähnliche, auch wenn ich eine Umwandlung eines neues ByteBuffer Objekt jedes Mal bin mit gemacht wird . Muss ich diese Methoden synchronisieren? Gibt es eine bessere Möglichkeit, zwischen Strings und ByteBuffers zu konvertieren? Vielen Dank!

DivideByHero
quelle
Es wäre hilfreich, den vollständigen Stack-Trace der Ausnahme zu sehen.
Michael Borgwardt

Antworten:

53

Überprüfen Sie die CharsetEncoderund CharsetDecoderAPI-Beschreibungen - Sie sollten eine bestimmte Folge von Methodenaufrufen befolgen , um dieses Problem zu vermeiden. Zum Beispiel für CharsetEncoder:

  1. Setzen Sie den Encoder über die resetMethode zurück, es sei denn, er wurde zuvor noch nicht verwendet.
  2. Rufen Sie die encodeMethode null oder mehrmals auf, solange zusätzliche Eingaben verfügbar sind, übergeben Sie falsedas Argument endOfInput, füllen Sie den Eingabepuffer und leeren Sie den Ausgabepuffer zwischen den Aufrufen.
  3. Rufen Sie die encodeMethode ein letztes Mal auf und übergeben Sie truedas Argument endOfInput. und dann
  4. Rufen Sie die flushMethode auf, damit der Encoder jeden internen Status in den Ausgabepuffer leeren kann.

Übrigens ist dies der gleiche Ansatz, den ich für NIO verwende, obwohl einige meiner Kollegen jedes Zeichen direkt in ein Byte konvertieren, da sie nur ASCII verwenden, was ich mir wahrscheinlich schneller vorstellen kann.

Adamski
quelle
2
Vielen Dank, das war sehr hilfreich! Ich stellte fest, dass ich mehrere Threads hatte, die meine Konvertierungsfunktionen gleichzeitig aufriefen, obwohl ich sie nicht so konzipiert hatte, dass dies möglich war. Ich habe das Problem behoben, indem ich charset.newEncoder (). Encode () und charset.newDecoder (). Decode () aufgerufen habe, um sicherzustellen, dass ich jedes Mal einen neuen Encoder / Decoder verwende, um Parallelitätsprobleme zu vermeiden oder unnötig auf diesen Objekten synchronisieren zu müssen. die in meinem Fall keine aussagekräftigen Daten teilen. Ich habe auch einige Tests durchgeführt und bei jeder Verwendung von newEncoder () / newDecoder () keinen messbaren Leistungsunterschied festgestellt!
DivideByHero
3
Kein Problem. Sie könnten vermeiden, jedes Mal neue Encoder / Decoder erstellen zu müssen, aber dennoch threadsicher bleiben, indem Sie ThreadLocal verwenden und nach Bedarf träge einen dedizierten Encoder / Decoder pro Thread erstellen (das habe ich getan).
Adamski
1
Könnte das funktionieren? neuer String (bb.array (), 0, bb.array (). Länge, "UTF-8")
bentech
36

Wenn sich nichts geändert hat, sind Sie besser dran

public static ByteBuffer str_to_bb(String msg, Charset charset){
    return ByteBuffer.wrap(msg.getBytes(charset));
}

public static String bb_to_str(ByteBuffer buffer, Charset charset){
    byte[] bytes;
    if(buffer.hasArray()) {
        bytes = buffer.array();
    } else {
        bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
    }
    return new String(bytes, charset);
}

Normalerweise ist buffer.hasArray () je nach Anwendungsfall entweder immer wahr oder immer falsch. In der Praxis ist es sicher, den nicht benötigten Zweig zu optimieren, es sei denn, Sie möchten wirklich, dass er unter keinen Umständen funktioniert.

Fuwjax
quelle
14

Die Antwort von Adamski ist gut und beschreibt die Schritte in einer Codierungsoperation, wenn die allgemeine Codierungsmethode verwendet wird (die einen Bytepuffer als eine der Eingaben verwendet).

Die fragliche Methode (in dieser Diskussion) ist jedoch eine Variante von encode - encode (CharBuffer in) . Dies ist eine bequeme Methode, die den gesamten Codierungsvorgang implementiert . (Siehe Java-Dokumentreferenz in PS)

Gemäß den Dokumenten sollte diese Methode daher nicht aufgerufen werden, wenn bereits eine Codierungsoperation ausgeführt wird (was im ZenBlender-Code geschieht - unter Verwendung eines statischen Codierers / Decodierers in einer Umgebung mit mehreren Threads).

Persönlich verwende ich gerne Convenience- Methoden (gegenüber den allgemeineren Codierungs- / Decodierungsmethoden), da sie die Belastung verringern, indem sie alle Schritte unter der Abdeckung ausführen.

ZenBlender und Adamski haben in ihren Kommentaren bereits mehrere Möglichkeiten vorgeschlagen, dies sicher zu tun. Listen Sie sie alle hier auf:

  • Erstellen Sie bei Bedarf für jede Operation ein neues Encoder / Decoder-Objekt (nicht effizient, da dies zu einer großen Anzahl von Objekten führen kann). ODER,
  • Verwenden Sie einen ThreadLocal, um zu vermeiden, dass für jede Operation ein neuer Encoder / Decoder erstellt wird. ODER,
  • Synchronisieren Sie den gesamten Kodierungs- / Dekodierungsvorgang (dies wird möglicherweise nicht bevorzugt, es sei denn, es ist für Ihr Programm in Ordnung, auf Parallelität zu verzichten).

PS

Java Docs Referenzen:

  1. Codierungsmethode (Convenience): http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer%29
  2. Allgemeine Codierungsmethode: http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer,%20java.nio.ByteBuffer,%20boolean% 29
Gurpsin
quelle