web-dev-qa-db-fra.com

Java: Conversion de chaîne vers et depuis ByteBuffer et problèmes associés

J'utilise Java NIO pour mes connexions de socket et mon protocole est basé sur du texte. Je dois donc pouvoir convertir des chaînes en ByteBuffers avant de les écrire dans SocketChannel et reconvertir les ByteBuffers entrants. Pour Strings, j'utilise actuellement ce 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;
}

Cela fonctionne la plupart du temps, mais je me demande si c'est le moyen préféré (ou le plus simple) de faire chaque direction de cette conversion, ou s'il existe un autre moyen d'essayer. Parfois, et apparemment au hasard, les appels à encode() et decode() lèveront une exception Java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END, Ou similaire, même si j'utilise un nouvel objet ByteBuffer à chaque fois la conversion est terminée. Dois-je synchroniser ces méthodes? Un meilleur moyen de convertir entre Strings et ByteBuffers? Merci!

79
DivideByHero

Découvrez les descriptions de l'API CharsetEncoder et CharsetDecoder - Vous devez suivre une séquence de méthodes spécifique appelle pour éviter ce problème. Par exemple, pour CharsetEncoder:

  1. Réinitialisez le codeur via la méthode reset, à moins qu’il n’ait été utilisé auparavant;
  2. Appelez la méthode encode zéro ou plusieurs fois, tant qu'une entrée supplémentaire peut être disponible, en transmettant false pour l'argument endOfInput, en remplissant le tampon d'entrée et en vidant le tampon de sortie entre les invocations;
  3. Appelez la méthode encode une dernière fois en passant true pour l'argument endOfInput; et alors
  4. Appelez la méthode flush pour que le codeur puisse vider tout état interne dans la mémoire tampon de sortie.

En passant, c’est la même approche que j’utilise pour NIO bien que certains de mes collègues convertissent directement chaque caractère en octet, sachant qu’ils utilisent uniquement l’ASCII, ce qui, j’imagine, est probablement plus rapide.

52
Adamski

À moins que les choses changent, il vaut mieux avec

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

En règle générale, buffer.hasArray () sera toujours vrai ou toujours faux en fonction de votre cas d'utilisation. En pratique, à moins que vous ne vouliez vraiment que cela fonctionne, il est prudent d'optimiser la succursale dont vous n'avez pas besoin.

28
Fuwjax

Answer by Adamski est bon et décrit les étapes d’une opération de codage lors de l’utilisation de la méthode de codage général (qui utilise un tampon d’octets comme l’une des entrées).

Cependant, la méthode en question (dans cette discussion) est une variante de encoder - encoder (CharBuffer dans) . Il s'agit d'une méthode pratique qui implémente la totalité de l'opération de codage . (Veuillez consulter Java dans P.S.)

Selon la documentation, Cette méthode ne doit donc pas être invoquée si une opération de codage est déjà en cours (ce qui se passe dans le code de ZenBlender - en utilisant codeur/décodeur statique dans un environnement multithread).

Personnellement, j'aime utiliser les méthodes commodité (par rapport aux méthodes plus générales d’encodage/décodage), car elles suppriment la charge en effectuant toutes les étapes à l’écart.

ZenBlender et Adamski ont déjà suggéré plusieurs façons de le faire en toute sécurité dans leurs commentaires. Les listant tous ici:

  • Créez un nouvel objet codeur/décodeur lorsque vous en avez besoin pour chaque opération (inefficace car cela pourrait entraîner un grand nombre d'objets). OU,
  • Utilisez un ThreadLocal pour éviter de créer un nouveau codeur/décodeur pour chaque opération. OU,
  • Synchronisez l'intégralité de l'opération d'encodage/décodage (cette option pourrait ne pas être préférée à moins de sacrifier un accès concurrentiel est correct pour votre programme)

P.S.

Références de la documentation Java:

  1. Méthode Encode (commodité): http://docs.Oracle.com/javase/6/docs/api/Java/nio/charset/CharsetEncoder.html#encode%28Java.nio.CharBuffer%29
  2. Méthode d'encodage général: http://docs.Oracle.com/javase/6/docs/api/Java/nio/charset/CharsetEncoder.html#encode%28Java.nio.CharBuffer,%20Java.nio.ByteBuffer ,% 20boolean% 29
14
gurpsin