2009-05-18 10 views
20

J'ai besoin de coder/décoder des tableaux d'octets UTF-16 vers et à partir de java.lang.String. Les tableaux d'octets me sont donnés avec un Byte Order Marker (BOM), et j'ai besoin d'encoder des tableaux d'octets avec une nomenclature.Comment encoder/décoder des tableaux d'octets UTF-16LE avec une nomenclature?

En outre, parce que j'ai affaire à un client/serveur Microsoft, j'aimerais émettre l'encodage en little endian (avec le LE BOM) pour éviter tout malentendu. Je me rends compte qu'avec la nomenclature, cela devrait fonctionner, mais je ne veux pas nager en amont dans le monde de Windows.

À titre d'exemple, est une méthode ici qui code pour une java.lang.String comme dans UTF-16 little endian avec une nomenclature:

public static byte[] encodeString(String message) { 

    byte[] tmp = null; 
    try { 
     tmp = message.getBytes("UTF-16LE"); 
    } catch(UnsupportedEncodingException e) { 
     // should not possible 
     AssertionError ae = 
     new AssertionError("Could not encode UTF-16LE"); 
     ae.initCause(e); 
     throw ae; 
    } 

    // use brute force method to add BOM 
    byte[] utf16lemessage = new byte[2 + tmp.length]; 
    utf16lemessage[0] = (byte)0xFF; 
    utf16lemessage[1] = (byte)0xFE; 
    System.arraycopy(tmp, 0, 
        utf16lemessage, 2, 
        tmp.length); 
    return utf16lemessage; 
} 

Quelle est la meilleure façon de le faire en Java? Idéalement, je voudrais éviter de copier tout le tableau d'octets dans un nouveau tableau d'octets qui a deux octets supplémentaires alloués au début.

va de même pour le décodage d'une telle chaîne, mais qui est beaucoup plus simple en utilisant le java.lang.String constructor:

public String(byte[] bytes, 
       int offset, 
       int length, 
       String charsetName) 

Répondre

27

Le nom charset « UTF-16 » sera toujours encoder avec une nomenclature et décode données à l'aide big/little endianness, mais "UnicodeBig" et "UnicodeLittle" sont utiles pour l'encodage dans un ordre d'octets spécifique. Utilisez UTF-16LE ou UTF-16BE pour aucune BOM - see this post pour savoir comment utiliser "\ uFEFF" pour gérer les nomenclatures manuellement. Voir here pour la dénomination canonique des noms de chaînes charset ou (de préférence) la classe Charset. Prenez également note que seulement limited subset of encodings sont absolument nécessaires pour être pris en charge.

+1

Merci! Un autre problème cependant ... Utiliser "UTF-16" encode les données comme Big Endian, que je soupçonne ne pas aller bien avec les données de Microsoft (même si la nomenclature existe). Un moyen d'encoder UTF-16LE avec BOM avec Java? Je vais mettre à jour ma question pour refléter ce que je cherchais vraiment ... –

+0

Cliquez sur le lien "voir ce message" qu'il a posté. Fondamentalement, vous bourrez un caractère \ uFEFF au début de votre chaîne, puis encoder en UTF-16LE, et le résultat aura une nomenclature correcte. –

+0

Utilisez "UnicodeLittle" (en supposant que votre JRE le supporte - ("\ uEFFF" + "ma chaîne"). GetBytes ("UTF-16LE") sinon). Bien que je serais surpris si les API de Microsoft s'attendaient à une nomenclature mais ne pouvaient pas gérer les données big-endian, ils préfèrent utiliser les nomenclatures plus que les autres. Testez avec des chaînes vides - vous pouvez obtenir des tableaux vides s'il n'y a pas de données. – McDowell

2
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(string.length() * 2 + 2); 
    byteArrayOutputStream.write(new byte[]{(byte)0xFF,(byte)0xFE}); 
    byteArrayOutputStream.write(string.getBytes("UTF-16LE")); 
    return byteArrayOutputStream.toByteArray(); 

EDIT: En relisant votre question, je vois que vous préféreriez éviter complètement l'allocation de double matrice. Malheureusement, l'API ne vous donne pas cela, autant que je sache. (Il y avait une méthode, mais elle est déconseillée, et vous ne pouvez pas spécifier d'encodage avec).

J'ai écrit ce qui précède avant que j'ai vu votre commentaire, je pense que la réponse à utiliser les classes nio est sur la bonne voie. Je regardais cela, mais je ne suis pas assez familier avec l'API pour savoir comment vous y arrivez.

+0

Merci. De plus, ce que j'aurais aimé ici, c'est de ne pas allouer tout le tableau d'octets avec string.getBytes ("UTF-16LE") - peut-être en encapsulant le flux en InputStream, ce qui était le point de ma précédente question: http://stackoverflow.com/questions/837703/how-can-i-get-a-java-io-inputstream-from-a-java-lang-string –

+0

Notez que ce code alloue réellement des tableaux assez grands pour la chaîne trois fois, puisque vous avez le tableau interne du ByteArrayOutputStream qui est copié dans l'appel .toByteArray(). Un moyen de redescendre à l'allocation de deux est d'envelopper le ByteArrayOutputStream dans un OutputStreamWriter et écrire la chaîne à cela. Ensuite, vous avez encore l'état interne et la copie faite par .toByteArray(), mais pas la valeur de retour de .getBytes –

+0

Il semble que vous êtes juste échangez un tableau de caractères pour un tableau d'octets du ByteArrayOutputStream si vous faites cela, les délégués de OutputStreamWriter à la classe StreamEncoder, qui crée un tampon char [] pour récupérer les données String. String est immuable, et la taille d'un tableau est invariable, de sorte que la copie semble inévitable. Je pense que nio est censé aider avec cette double création sur le ByteArrayOutputStream – Yishai

6

Tout d'abord, pour le décodage, vous pouvez utiliser le jeu de caractères "UTF-16"; qui détecte automatiquement une nomenclature initiale. Pour l'encodage UTF-16BE, vous pouvez également utiliser le jeu de caractères "UTF-16" - qui écrira une nomenclature correcte, puis affichera des caractères big endian.

Pour encoder à little endian avec une nomenclature, je ne pense pas que votre code actuel soit trop mauvais, même avec la double allocation (sauf si vos chaînes sont vraiment monstrueuses). Ce que vous voudrez peut-être faire si ce n'est pas un tableau d'octets, mais plutôt un ByteBuffer java.nio, et utilisez la classe java.nio.charset.CharsetEncoder. (Ce que vous pouvez obtenir à partir de Charset.forName ("UTF-16LE"). NewEncoder()).

+0

Merci, bon conseil. –

7

Voici comment vous le faites dans NIO:

return Charset.forName("UTF-16LE").encode(message) 
      .put(0, (byte) 0xFF) 
      .put(1, (byte) 0xFE) 
      .array(); 

Il est certainement censé être plus rapide, mais je ne sais pas combien de tableaux il est sous les couvertures, mais ma compréhension du point de l'API est qu'il est censé minimiser cela.

+0

Celui-ci ne fonctionne pas réellement. Les appels put (0) et put (1) écrasent les deux premiers octets du ByteBuffer du message codé. – hopia

0

Ceci est une vieille question, mais encore, je ne pouvais pas trouver une réponse acceptable pour ma situation. Fondamentalement, Java n'a pas de codeur intégré pour UTF-16LE avec une nomenclature. Et donc, vous devez déployer votre propre implémentation.

Voici ce que j'ai fini avec:

private byte[] encodeUTF16LEWithBOM(final String s) { 
    ByteBuffer content = Charset.forName("UTF-16LE").encode(s); 
    byte[] bom = { (byte) 0xff, (byte) 0xfe }; 
    return ByteBuffer.allocate(content.capacity() + bom.length).put(bom).put(content).array(); 
} 
Questions connexes