Les classes CharsetEncoder et CharsetDecoder gèrent les erreurs de codage par un appel aux méthodes onMalformedInput() et onUnmappableCharacter() définies dans les classes CharsetDecoder et CharsetEncoder.

Charset utf16 = Charset.forName("UTF-16");
CharBuffer cb = utf16.newEncoder()
                     .onMalformedInput(CodingErrorAction.REPLACE)
                     .onUnmappableCharacter(CodingErrorAction.REPLACE)
                     .encode(bb);

ByteBuffer bb = utf16.newDecoder()
                     .onMalformedInput(CodingErrorAction.REPLACE)
                     .onUnmappableCharacter(CodingErrorAction.REPLACE)
                     .decode(bb);

Les méthodes onMalformedInput() et onUnmappableCharacter() sont chargées de traiter les erreurs de codage (entrée mal formée et caractère non référencé) en appliquant une action appropriée. Ces actions sont énumérées dans la classe CodingErrorAction.

ActionDescription
IGNOREindique qu'une erreur de codage doit être traitée de telle manière que l'entrée incorrecte soit ignorée et en reprenant l'opération de codage.
REPLACEindique qu'une erreur de codage doit être traitée de telle sorte que l'entrée incorrecte soit ignorée et que la valeur de remplacement soit ajoutée au tampon, puis l'oparation de codage reprend.
REPORTindique qu'une erreur de codage doit être signalée soit par le retour d'un objet CoderResult, soit par la levée d'une exception CharacterCodingException. L'opération de codage est arrêtée.

La valeur de remplacement d'une erreur de codage, dot être définie en invoquant la méthode replaceWith(). L'obtention de cette valeur s'effectue via un appel à la méthode replacement().

CharsetDecoder cs = decodeur.replaceWith("_");
String valeur = cs.replacement();

byte[] remplacement = new byte[]{99, 104, 97};
if(encodeur.isLegalReplacement(remplacement)){
    CharsetEncoder ce = encodeur.replaceWith(remplacement);
    byte[] valeur = ce.replacement();
}

La classe CharsetEncoder possède une méthode isLegalReplacement(), déterminant si le tableau d'octets passé en argument consitue une valeur de remplacement autorisée.

Les méthodes malformedInputAction() et unmappableCharacterAction() retournent les actions d'erreur de codage.

CodingErrorAction actionEntreeIncorrecte = 
                        utf16.malformedInputAction();
CodingErrorAction actionCaractereInconnu = 
                        utf16.unmappableCharacterAction();

La classe CharsetDecoder possède deux méthode décode(). La première prend un argument ByteBuffer, alors que la seconde en prend trois, deux tampons ByteBuffer (entrée) et CharBuffer (sortie) et une valeur booléenne, indiquant si l'appelant ne peut fournir aucune entrée supplémentaires au delà de ceux compris dans le tampon. Dans le cas, d'une erreur de codage, la première lévera une exception, tandis que la seconde retournera un objet CoderResult.

ByteBuffer entree = ByteBuffer.allocate(1024);
//remplissage du tampon d'octets
Charset latin = Charset.forName("ISO-8859-1");
CharsetDecoder decodeur = latin.newDecoder();
CharBuffer sortie = decodeur.decode(entree);
//ou
CharBuffer sortie = CharBuffer.allocate(1024);
CoderResult erreur = decodeur.decode(entree, sortie, false);

De la même façon, il existe deux méthodes encode() dans la classe CharsetEncoder. Les méthodes canEncode() indiquent si la séquence de caractères ou un seul caractère peut être encodée en bloc d'octets.

CharBuffer entree = CharBuffer.wrap("Une séquence d'entrée...");
Charset latin = Charset.forName("ISO-8859-1");
CharsetEncoder encodeur = latin.newEncoder();
if(encodeur.canEncode(entree)){
    ByteBuffer sortie = encodeur.encode(entree);
    //ou
    ByteBuffer sortie = CharBuffer.allocate(entree.length());
    CoderResult erreur = encodeur.encode(entree, sortie, false);
}

Un objet CoderResult détient des informations à propos du résultat de l'opération de codage. Une opération d'encodage ou de décodage est susceptible de se terminer pour :

  • Une place insuffisante dans le tampon de sortie (static CoderResult.Overflow).
  • Le tampon d'entrée a été complètement rempli ou il n'est pas encore vide alors qu'une entrée supplémentaire a été requise (static CoderResult.Underflow).
  • Une entrée mal formée est signalée.
  • Un caractère inconnu a été rencontré.

La classe CoderResult possède plusieurs méthodes déterminant le type du résultat de codage.

ActionDescription
boolean isError()indique si l'objet courant décrit une condition d'erreur (entrée mal formée ou caractère inconnu).
boolean isMalformed()indique si l'objet décrit une erreur due à une entrée mal formée.
boolean isOverflow()indique si l'objet décrit une condition de débordement.
boolean isUnderflow()indique si l'objet décrit une condition d'incomplétude.
boolean isUnmappable()indique si l'objet décrit une erreur due à un caractère non référencé.

Il est possible de récupérer la longueur de l'entrée incorrecte par l'intermédiaire de la méthode length().

int longueur = resultat.length();

Des méthodes statiques de création d'objets CoderResult spécifiques complètent la panoplie de cette classe.

CoderResult erreurMalFormee = CoderResult.malformedForLength(1);

CoderResult erreurCaractere = CoderResult.unmappableForLength(1);

Deux méthodes permettent d'obtenir une approximation de la taille du tampon de sortie par rapport au tampon d'entrée.

La valeur moyenne obtenue par les méthodes averageCharsPerByte() (CharsetDecoder) et averageBytesPerChar() (CharsetEncoder) fournit une estimation de la taille du tampon de sortie (CharBuffer) ou ByteBuffer) en fonction d'une séquence d'entrée donnée.

CharsetEncoder ce = utf16.newEncoder();
float moy = utf16.averageBytesPerChar();

CharsetDecoder cd = utf16.newDecoder();
float moy = utf16.averageCharsPerByte();

Les méthodes maxCharsPerByte() et maxBytesPerChar() donnent un nombre maximum de caractères ou d'octets qui seraient produits pour respectivement chaque octet ou caractère d'entrée. Cette valeur peut servir à calculer la taille maximum du tampon de sortie requise pour une séquence d'entrée.

CharsetEncoder ce = utf16.newEncoder();
float max = utf16.maxBytesPerChar();

CharsetDecoder cd = utf16.newDecoder();
float max = utf16.maxCharsPerByte();

La réinitialisation des codeurs permet un effacement de l'état interne de l'objet courant. La méthode reset() est conçu à cet effet, toutefois, elle appelle une méthode protégée (implReset()) qui ne fait rien dans l'implémentation par défaut. Ainsi, pour obtenir un réel effacement de l'état interne d'un codeur, il faut surcharger la méthode implReset() dans la sous-classe d'un codeur.

public class Decodeur extends CharsetDecoder {
    //...
    protected void implReset(){
        super.charset = null;
        super.replacement = null;
        //...
    }
    //...
}
Exemple [voir]
package nio;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;

public class FichierIO {
    private String encodage;

    public static void main(String[] args) {
        FichierIO io = new FichierIO("UTF-16");
        File fichier = new File("document.txt");
        io.ecrire("Un contenu quelconque...", fichier, false);
        System.out.println("Contenu :\n" + io.lire(fichier));
    }

    public FichierIO(String encodage) {
        this.encodage = encodage;
    }

    public void ecrire(String contenu, File fichier, boolean ajout) {
        try {
            FileOutputStream fos = new FileOutputStream(fichier, ajout);
            FileChannel fc = fos.getChannel();

            Charset latin = Charset.forName(this.encodage);
            CharsetEncoder encodeur = latin.newEncoder();

            if (contenu == null)
                return;
            CharBuffer cb = CharBuffer.wrap(contenu);
            int taille = (int)(cb.length() * encodeur.maxBytesPerChar());
            ByteBuffer bb = ByteBuffer.allocate(taille);
            System.out.println(this.getEtatInterne(encodeur));
            CoderResult code = encodeur.encode(cb, bb, false);
            if (code.isMalformed() || code.isUnmappable())
                System.out.println("Une erreur d'écriture s'est produite !");
            cb.flip();
            bb.flip();
            System.out.println("Taille Caractères / Octets / Maximum : " 
                                + contenu.length() + " / " 
                                + bb.limit() + " / " 
                                + taille);
            System.out.println("Ecriture du fichier " + fichier.toString());
            fc.write(bb);

            fc.close();
        }
        catch (java.io.FileNotFoundException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String lire(File fichier) {
        try {
            if (fichier.exists()) {
                FileInputStream fis = new FileInputStream(fichier);
                FileChannel fc = fis.getChannel();
                int taille = (int) fc.size();
                MappedByteBuffer mpb = 
                                fc.map(FileChannel.MapMode.READ_ONLY, 0, taille);

                Charset latin = Charset.forName(this.encodage);
                CharsetDecoder decodeur = latin.newDecoder();
                CharBuffer cb = CharBuffer.allocate(taille);
                System.out.println(this.getEtatInterne(decodeur));
                CoderResult code = decodeur.decode(mpb, cb, false);
                if (code.isMalformed() || code.isUnmappable())
                    System.out.println("Une erreur de lecture s'est produite !");
                mpb.flip();
                cb.flip();
                fc.close();
                System.out.println("Lecture du fichier " + fichier.toString());
                return cb.toString();
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    private String getEtatInterne(Object codeur) {
        StringBuffer res = new StringBuffer("Jeu de caractères : ");
        if(codeur instanceof CharsetDecoder) {
            CharsetDecoder cd = (CharsetDecoder)codeur;
            res.append(cd.charset().toString());
            res.append("\nMoyenne de caractères par octet : ");
            res.append(cd.averageCharsPerByte());
            res.append("\nNombre de caractères maximum par octet : ");
            res.append(cd.maxCharsPerByte());
        }
        else if(codeur instanceof CharsetEncoder) {
            CharsetEncoder ce = (CharsetEncoder)codeur;
            res.append(ce.charset().toString());
            res.append("\nMoyenne d'octets par caractère : ");
            res.append(ce.averageBytesPerChar());
            res.append("\nNombre d'octets maximum par caractère : ");
            res.append(ce.maxBytesPerChar());
        }
        return res.toString();
    }
}