2010-01-27 4 views
4

EDIT: J'ai été convaincu que cette question est quelque peu non-sensée. Merci à ceux qui ont répondu. Je peux poster une question de suivi plus spécifique.Java 1.6 L'encodage de Windows-1252 échoue sur 3 caractères

Aujourd'hui, j'investissais des problèmes d'encodage et écrit ce test unitaire pour isoler une base repro cas:

int badCount = 0; 
for (int i = 1; i < 255; i++) { 
    String str = "Hi " + new String(new char[] { (char) i }); 

    String toLatin1 = new String(str.getBytes("UTF-8"), "latin1"); 
    assertEquals(str, new String(toLatin1.getBytes("latin1"), "UTF-8")); 

    String toWin1252 = new String(str.getBytes("UTF-8"), "Windows-1252"); 
    String fromWin1252 = new String(toWin1252.getBytes("Windows-1252"), "UTF-8"); 

    if (!str.equals(fromWin1252)) { 
     System.out.println("Can't encode: " + i + " - " + str + 
          " - encodes as: " + fromWin1252); 
     badCount++; 
    } 
} 

System.out.println("Bad count: " + badCount); 

La sortie:

 
    Can't encode: 129 - Hi ? - encodes as: Hi ?? 
    Can't encode: 141 - Hi ? - encodes as: Hi ?? 
    Can't encode: 143 - Hi ? - encodes as: Hi ?? 
    Can't encode: 144 - Hi ? - encodes as: Hi ?? 
    Can't encode: 157 - Hi ? - encodes as: Hi ?? 
    Can't encode: 193 - Hi Á - encodes as: Hi ?? 
    Can't encode: 205 - Hi Í - encodes as: Hi ?? 
    Can't encode: 207 - Hi Ï - encodes as: Hi ?? 
    Can't encode: 208 - Hi ? - encodes as: Hi ?? 
    Can't encode: 221 - Hi ? - encodes as: Hi ?? 
    Bad count: 10 

1.6.0_07 sous Mac JDK OS 10.6. Mon

Latin1 symétrique allie encode tous les 254 caractères. Windows-1252 non. Les trois caractères imprimables (193, 205, 207) sont les mêmes codes dans Latin1 et Windows-1252, donc je ne m'attendrais à aucun problème.

Quelqu'un peut-il expliquer ce comportement? Est-ce un bug JDK?

- James

+0

Le code que vous publiez n'a aucun sens (obtenir des données codées UTF-8 à partir d'une chaîne et les interpréter * comme s'il s'agissait de latin1 *). Par conséquent, il est assez difficile de suivre ce que vous essayez de faire. –

+0

J'ai des données encodées en UTF-8 que j'ai besoin de transcoder en Windows-1252. Dans mon système de production, j'ai remarqué que cela échouait sur le personnage 193, j'ai donc écrit ce cas de repro de base et à ma grande surprise, j'ai découvert que ces 10 caractères ne codaient pas symétriquement entre Windows-1252 et UTF-8. Notez que tous les 254 caractères peuvent être encodés entre UTF-8 et Latin1. D'où ma surprise et ma confusion. Est ce que ça aide? –

+0

Qu'est-ce que ** exactement ** voulez-vous dire par "transcoder"? Voulez-vous les octets codés Windows-1252 qui représentent les mêmes glyphes? Si c'est le cas, alors vous devez être conscient que ce n'est tout simplement pas possible dans tous les cas, car UTF-8 peut représenter tous les caractères Unicode, alors que Windows-1252 ne peut évidemment pas le faire. –

Répondre

4

À mon avis, le programme d'essais est profondément erronée, car elle rend les transformations efficacement inutiles entre les chaînes sans signification sémantique.

Si vous voulez vérifier si toutes les valeurs d'octets sont des valeurs valides pour un encodage donné, quelque chose comme cela pourrait être plus comme ça:

public static void tryEncoding(final String encoding) throws UnsupportedEncodingException { 
    int badCount = 0; 
    for (int i = 1; i < 255; i++) { 
     byte[] bytes = new byte[] { (byte) i }; 

     String toString = new String(bytes, encoding); 
     byte[] fromString = toString.getBytes(encoding); 

     if (!Arrays.equals(bytes, fromString)) { 
      System.out.println("Can't encode: " + i + " - in: " + Arrays.toString(bytes) + "/ out: " 
        + Arrays.toString(fromString) + " - result: " + toString); 
      badCount++; 
     } 
    } 

    System.out.println("Bad count: " + badCount); 
} 

Notez que ce programme teste de test entrées en utilisant la (usnigned) valeurs de octet 1 à 255. le code de la question utilise les valeurs char (équivalent à Unicode codets dans cette gamme) de 1 à 255.

Essayez d'imprimer les tableaux d'octets réelles traitées par le programme dans le exemple et vous voyez que vous ne vérifie pas réellement toutes les valeurs d'octets et que certaines de vos "mauvaises" correspondances sont des doublons d'autres.

L'exécution de ce avec "Windows-1252" comme argument produit cette sortie:

 
Can't encode: 129 - in: [-127]/ out: [63] - result: � 
Can't encode: 141 - in: [-115]/ out: [63] - result: � 
Can't encode: 143 - in: [-113]/ out: [63] - result: � 
Can't encode: 144 - in: [-112]/ out: [63] - result: � 
Can't encode: 157 - in: [-99]/ out: [63] - result: � 
Bad count: 5 

Ce qui nous dit que Windows-1252 n'accepte pas l'octet valeurs 129, 1441, 143, 144 et 157 comme des valeurs valides. (Note: je parle de valeurs d'octets non signés ici Le code ci-dessus montre -127, -115, ... parce que Java ne connaît que des octets non signés).

The Wikipedia article on Windows-1252 semble vérifier cette observation en déclarant ceci:

Selon les informations sur Microsoft et les sites Web du Consortium Unicode, les positions 81, 8D, 8F, 90 et 9D ne sont pas utilisés

+0

Joachim, merci pour ce test. Notez que les caractères 193, 205 et 207 ne figurent pas dans votre sortie ci-dessus. Pourquoi ne codent-ils pas correctement dans Windows-1252, mais ils le font dans Latin1? Ce code correspond au même caractère dans les deux pages de code. –

+0

@James: "Pourquoi ne codent-ils pas correctement sous Windows-1252" est la mauvaise question. Le caractère U + 00C1 (codepoint 193) est représenté par 0xC3 0x81 dans UTF-8. Lorsque vous essayez d'interpréter ces octets comme Windows-1252, vous remarquerez que 0x81 n'est pas une valeur valide pour Windows-1252 et sera remplacé par un caractère de remplacement. –

+0

Cela a du sens. Je vous remercie. Je dois ouvrir une nouvelle question, car celle-ci porte à confusion. Mes excuses. –

2

Ce que votre code fait (String->byte[]->String, deux fois) est à peu près le opposé de transcodage, et n'a aucun sens du tout (il est pratiquement garanti de perdre des données).byte[]->String->byte[] signifie Transcodage:

public byte[] transcode(byte[] input, String inputEnc, String targetEnc) 
{ 
    return new String(input, inputEnc).getBytes(targetEnc); 
} 

Et bien sûr, il va perdre des données lorsque l'entrée contient des caractères que l'encodage cible ne prend pas en charge.

+0

Vous ne savez pas en quoi cela diffère de mon exemple. Pourriez-vous poster un exemple qui démontre que cela transcode réellement entre les encodages? Mes tests indiquent que le code fait exactement ce que le mien fait. Si vous avez un tableau d'octets codé en UTF-8, et passez "Windows-1252" comme encodage cible, vous ne récupérerez pas une chaîne correctement encodée - vous obtiendrez du charabia. Voir mon implémentation Charset transcode(). Je pense que c'est ce que nous recherchons. –

+0

@James il semble que vous ayez des idées fausses sur les chaînes Java. Ce sont des caractères * décodés * (en utilisant l'UTF-16 en interne, mais ce n'est pas pertinent ici). Vous ne pouvez pas décoder une chaîne. Les tableaux d'octets sont décodés en chaînes et les chaînes sont codées en tableaux d'octets. Le transcodage commence et se termine par des tableaux d'octets, car un tableau d'octets est une représentation concrète, dépendant du codage, d'une chaîne abstraite. –

+0

Merci. Je maintiens une application où la chaîne a été créée incorrectement en amont (dans un code DAO, en raison de données incorrectement stockées dans MySQL). Les octets bruts étaient UTF-8, mais la chaîne a été créée avec Windows-1252. Mon but était de prendre une chaîne Java, ce qui est tout ce que j'ai à ce stade, et de le transformer en quelque sorte, donc ce n'est pas du charabia. Je me rends compte que je ne suis pas en train de résoudre les causes profondes, etc., mais c'est parfois notre problème en ingénierie de maintenance. La réponse de Jochaim que 0x81 n'est pas définie dans Windows-1252 explique pourquoi je ne peux pas récupérer ce caractère. –

Questions connexes