Java: conversion des chaînes vers et depuis ByteBuffer et problèmes associés

j'utilise Java NIO pour mes connexions socket, et mon protocole est basé sur le texte, donc je dois être capable de convertir les chaînes en ByteBuffers avant de les écrire sur le SocketChannel, et de convertir les ByteBuffers entrants en chaînes. Actuellement, j'utilise 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 la façon préférée (ou la plus simple) de faire chaque direction de cette conversion, ou s'il y a une autre façon d'essayer. Occasionnellement, et apparemment au hasard, les appels à encode() et decode() lanceront un java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END exception, ou similaire, même si j'utilise un nouvel objet ByteBuffer chaque fois qu'une conversion est effectuée. Ai-je besoin de synchroniser ces méthodes? Y a-t-il une meilleure façon de convertir entre les cordes et les ByteBuffers? Merci!

76
demandé sur Jonas 2009-08-10 02:20:19

4 réponses

consultez les descriptions des API CharsetEncoder et CharsetDecoder pour éviter ce problème, suivez une séquence spécifique d'appels de méthodes . Par exemple, pour CharsetEncoder :

  1. réinitialiser l'encodeur par la méthode reset , sauf s'il n'a pas été utilisé auparavant;
  2. invoquer la méthode encode zéro ou plus de fois, aussi longtemps que des entrées supplémentaires peuvent être disponibles, en passant false pour l'argument endOfInput et en remplissant le tampon d'entrée et en vidangeant le tampon de sortie entre invocations;
  3. invoquer la méthode encode une dernière fois, en passant true pour l'argument endOfInput; et puis
  4. invoque la méthode flush pour que l'encodeur puisse vider n'importe quel état interne du tampon de sortie.

soit dit en passant, c'est le même approche que j'utilise pour le NIO bien que certains de mes collègues convertissent chaque char directement en un octet dans la connaissance qu'ils utilisent seulement ASCII, ce qui je peux imaginer est probablement plus rapide.

50
répondu Adamski 2013-07-29 10:25:20

à moins que les choses aient changé, vous êtes 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);
}

généralement tampon.hasArray() sera soit toujours vraie ou toujours fausse selon votre cas d'utilisation. Dans la pratique, sauf si vous voulez vraiment travailler en toutes circonstances, il est sûr d'optimiser loin la branche que vous n'avez pas besoin.

21
répondu Fuwjax 2015-08-13 19:07:24

Répondre par Adamski est bonne et décrit les étapes d'une opération de codage lors de l'utilisation de l'générale méthode encode (qui prend un tampon d'octets comme l'une des entrées)

Cependant, la méthode en question (dans cette discussion) est une variante de coder - encode(CharBuffer en) . Il s'agit d'une méthode de commodité qui implémente toute l'opération d'encodage . (Please see java docs reference in P. S.)

selon le docs, cette méthode ne doit donc pas être invoquée si une opération d'encodage est déjà en cours (ce qui se passe dans le code de ZenBlender -- en utilisant un encodeur/décodeur statique dans un environnement multi-threadé).

personnellement, j'aime utiliser les méthodes convenience (sur les méthodes plus générales d'encodage/décodage) comme ils enlèvent le fardeau en exécutant toutes les étapes sous les couvertures.

ZenBlender et Adamski ont déjà suggéré plusieurs façons d'y parvenir en toute sécurité dans leurs commentaires. En les énumérant tous ici:

  • créer un nouvel objet codeur/décodeur lorsque nécessaire pour chaque opération (pas efficace car il pourrait conduire à un grand nombre d'objets). OR,
  • utilisez un ThreadLocal pour éviter de créer de nouveaux encodeurs/décodeurs pour chaque opération. OR,
  • synchroniser tout l'encodage / décodage opération (cela peut ne pas être privilégié, à moins de sacrifier un peu de simultanéité est ok pour votre programme)

P. S.

java docs références:

  1. méthode D'encodage (convenience): http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer%29
  2. méthode générale d'encodage: http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer,%20java.nio.ByteBuffer,%20boolean%29
13
répondu gurpsin 2012-09-26 02:58:20
I am getting the same exception "java.lang.Illegalstate.exception when i run the below code
 protected static SAMStringResult execScript(ConfParams params, String routineName, String[] enquiryInParams) {
        SAMStringResult res = new SAMStringResult();
        res.setSuccess(false, null);
        res.message = "T24Tools.ExecScript: failed for unknown reason";
        res.setResult("");

        try {
            DefaultJConnectionFactory jcf = new DefaultJConnectionFactory();
            jcf.setHost(params.Host);
            jcf.setPort(params.Port);
            JConnection jc = jcf.getConnection(params.User, params.Password);
            JSubroutineParameters rtnParams = new JSubroutineParameters();
            JDynArray shellNameParam = new JDynArray(routineName);
            rtnParams.add(shellNameParam);
            JDynArray outputParam = new JDynArray(routineName);
            rtnParams.add(outputParam);
            JSubroutineParameters rtnOutParams = jc.call("SAM.EXEC.SHELL", rtnParams);
            res.setSuccess(true, null);
            res.message = "T24Tools.ExecScript: job executed";
            JDynArray resParam = rtnOutParams.get(1);
            String resStr = "";
            System.out.println("Parameterssize :" + resParam.getNumberOfAttributes());
            if (resParam != null) {
                for (int i = 0; i < 90145; i++) {
                    if (i > 0) {
                        resStr += "\n";
                    }
                    resStr += resParam.get(i + 1);
                }
            }
            String fixedResStr = removeInvalidXmlCharacters(resStr);
            res.setResult(fixedResStr);
            jc.close();
        } catch (Exception ex) {
            System.out.println("Exception in execScript :"+ex.getMessage());
            res.setSuccess(false, ex);
            res.message = "T24Tools.ExecScript: unexpected error ocured";
            res.setResult("");
        }
        return res;
    }

When i try to loop the for loop in my code 10,000 times. 
Code is working fine.
But the actual number of times it has to run is 90,145 times. In between the iteration this exception araises. I am unable to find the reason behind this exception.
can anyone help me in this?
-1
répondu Ashwini vijaykumar 2018-03-13 13:00:01