-2
votes

Flux d'octets Java caractères non anglais

J'ai lu ce code . Comme contenu xanadu.txt, utilisez "test". Le fichier a une taille de 4 octets. Si j'utilise debug pour exécuter out.write(c) un octet à la fois et après chaque fois ouvrir le fichier outagain.txt (avec le bloc-notes), je vois successivement: t -> te -> tes -> test. OK MAIS si nous changeons le contenu du fichier source (xanadu.txt) en grec (ou dans une autre langue) équivalent à test (τέστ), le fichier a maintenant une taille de 8 octets (je pense que parce que UTF nous avons 2 octets par caractère). Lors du débogage à nouveau, il apparaît un caractère hiéroglyphique sans signification à chaque fois que l' out.write(c) s'exécute. Lorsque le dernier octet (8e) s'imprime, le mot grec original (τέστ) apparaît soudainement. Pourquoi? De même si on choisit comme destination le flux de la console (dans netbeans) mais dans ce cas les caractères étranges restent à la fin si debug mais pas si on l'exécute normalement (!!!).


5 commentaires

avez-vous essayé de convertir le flux d'octets en une chaîne, puis d'utiliser la méthode string.charAt () pour obtenir chaque caractère de la chaîne?


@TheKNVB le problème, en principe, ne sera pas résolu de cette façon. Par exemple, une String composée d'un seul emoji peut avoir sa length() égale à 2 , donc charAt n'est pas une réponse universelle.


J'ai essayé le code suivant, cela fonctionne. public class MyClass {public static void main (String args []) {String data = "τέστ"; pour (int i = 0; i <data.length (); i ++) {System.out.println (data.charAt (i)); }}}


« Cela fonctionne dans ce cas précis » est généralement une mauvaise réponse, à moins que l'on ne donne plus de contexte et d'explications.


Αs j'ai compris que Stackoverflow est destiné uniquement aux questions les plus habiles des chercheurs de la NASA et du CERN et plus !!!! 😀


3 Réponses :


3
votes

Comme on observe, un seul char (16 bits en langage Java représentation interne) se transforme en un nombre variable d'octets dans une représentation de flux d'octets, en particulier UTF-8.

(Certains caractères occupent deux valeurs char ; je les ignorerai, mais la réponse s'applique toujours, mais plus encore)

Si vous produisez "par octet" comme dans votre expérience, dans certains cas, vous aurez un caractère fractionnaire. C'est une séquence illégale qui n'a aucun sens; certains logiciels (comme Notepad) essaieront néanmoins de lui donner un sens. Cela peut même inclure de deviner l'encodage. Par exemple, je ne sais pas que ce soit le cas, mais si le fichier n'est pas valide UTF-8 dans ses premiers octets - et nous savons que votre sortie d'un demi-caractère n'est pas valide UTF-8 - que peut-être que Notepad devine un encodage entièrement différent, qui traite la séquence d'octets comme une représentation valide de caractères entièrement différents.

tl; dr - poubelle, poubelle affichée.


0 commentaires

1
votes

Les ordinateurs modernes ont cette table gigantesque avec 4 milliards de caractères. Chaque caractère est identifié par un numéro unique de 32 bits. Tous les personnages auxquels vous pouvez penser se trouvent ici; du 'test' de base à 'τέστ' au bonhomme de neige (☃), à ceux spéciaux non visibles qui indiquent qu'un mot épelé de droite à gauche est en train de surgir, à un tas de ligatures (comme ff - qui est un seul caractère représentant la ligature ff), aux emoji, colorés et tout: 😀.

Cette réponse entière est essentiellement une séquence de ces nombres de 32 bits. Mais comment souhaitez-vous les stocker dans un fichier? C'est là que le «codage» entre en jeu. Il existe de très nombreux codages, et un problème crucial est que (presque) aucun codage n'est «détectable».

C'est comme ça:

Si un parfait inconnu s'approche de vous et vous dit "Hé!", Quelle langue parle-t-il? Probablement anglais. Mais peut-être le néerlandais, qui a aussi «Hey!». Cela pourrait aussi être japonais et ils ne vous saluent même pas, ils disent «oui» (plus ou moins). Comment saurais tu?

La réponse est, soit du contexte externe (si vous êtes au milieu de Newcastle, au Royaume-Uni, c'est probablement l'anglais), soit parce qu'ils vous le disent explicitement, mais l'un est, eh bien, externe, et l'autre n'est pas une pratique courante.

Les fichiers texte sont de la même manière .

Ils contiennent juste le texte encodé, ils n'indiquent pas de quel encodage il s'agit. Cela signifie que vous devez indiquer à l'éditeur, ou à votre newBufferedReader en java, ou à votre navigateur lors de l'enregistrement de ce contenu txt, le codage souhaité. Cependant, comme c'est ennuyeux d'avoir à faire à chaque fois, la plupart des systèmes ont un choix par défaut. Certains éditeurs de texte essaient même de comprendre de quel encodage il s'agit, mais tout comme cette personne dit «Hé! vous pourriez être anglais ou japonais, avec des interprétations très différentes, la même chose se produit avec cette estimation semi-intelligente de l'encodage de charset.

Cela nous amène à l'explication suivante:

  1. Vous écrivez τέστ dans votre éditeur et cliquez sur «enregistrer». Que fait votre éditeur? Enregistre-t-il en UTF-16? UTF-8? UCS-4? ISO-8859-7? Des fichiers complètement différents sont produits pour tous ces encodages! Étant donné qu'il fait 8 octets, cela signifie qu'il s'agit de UTF-16 ou UTF-8. Probablement UTF-8.

  2. Vous copiez ensuite ces octets un par un, ce qui est problématique: en UTF-8, un seul octet peut être la moitié d'un caractère. (Vous avez dit: UTF-8 stocke les caractères sur 2 octets; ce n'est pas vrai, UTF-8 stocke les caractères de telle sorte que chaque caractère fait 1, 2, 3 ou 4 octets; sa longueur variable par octet! - chaque caractère de τέστ est stocké comme 2 octets, cependant) - cela signifie que si vous avez copié, disons, 3 octets, la capacité de votre éditeur de texte à deviner ce que cela pourrait être est gravement entravée: il pourrait deviner UTF-8 mais se rendre compte ensuite qu'il n'est pas valide UTF-8 du tout (à cause de ce demi-caractère avec lequel vous vous êtes retrouvé), donc il devine mal et vous montre gobbledygook.

La leçon à tirer ici est double:

  1. Lorsque vous souhaitez traiter des caractères, utilisez char , Reader , Writer , String et d'autres éléments orientés caractères.

  2. Lorsque vous souhaitez traiter des octets, utilisez byte , byte[] , InputStream , OutputStream et d'autres éléments orientés octets.

  3. Ne faites jamais l'erreur que ces deux éléments sont facilement interchangeables, car ils ne le sont pas. Chaque fois que vous passez d'un `` monde '' à l'autre, vous DEVEZ spécifier le codage du jeu de caractères, car sinon, java choisit `` platform default '', ce que vous ne voulez pas (car maintenant vous avez un logiciel qui dépend d'un facteur externe et qui ne peut pas être testé. Yikes).

  4. Par défaut, UTF-8 pour tout ce que vous pouvez.


2 commentaires

Excellent. Une dernière question. Comment puis-je imprimer dans le fichier de destination le nombre de chaque octet au lieu de caractères? Puisque nous parlons de flux d'octets bruts et que c représente un octet entre 1 et 256, je m'attendrais à ce que out.write (c) imprime le numéro de chaque octet si nous ne spécifions pas un codage qui transforme ces séquences en fonction de la codage.


@nonlinearly out.write () est spécifié pour écrire un octet. Pas un tas d'ASCII qui représente cet octet en caractères (ce qui, hé, nous ramène à l'encodage! Maintenant, vous écrivez des caractères au lieu d'octets!). Heureusement, les chiffres 0 à 9 sont stockés dans la même séquence d'octets dans presque tous les encodages. try out.write(("" + number).getBytes(StandardCharsets.US_ASCII)); la bouchée est parce que vous essayez d'écrire des caractères dans un flux d'octets, ce qui n'est pas ce que vous devriez faire. Vous pouvez également ouvrir ce fichier en tant que flux de caractères (nouveau OutputStreamWriter (theOutputStream, StandardCharsets.US_ASCII)).



0
votes

tl; dr

Lire: Le minimum absolu que tout développeur de logiciel doit absolument et positivement savoir sur l'Unicode et les jeux de caractères (sans excuses!)

N'analysez pas les fichiers texte par octets (octets). Utilisez des classes spécialement conçues pour gérer le texte. Par exemple, utilisez Files et sa méthode readAllLines .

Détails

Notez au bas de cette page du didacticiel que ce n'est pas la bonne façon de gérer les fichiers texte:

CopyBytes semble être un programme normal, mais il représente en fait une sorte d'E / S de bas niveau que vous devriez éviter. Étant donné que xanadu.txt contient des données de caractères, la meilleure approche consiste à utiliser des flux de caractères, comme indiqué dans la section suivante.

Les fichiers texte peuvent utiliser ou non des octets uniques pour représenter des caractères uniques, tels que les fichiers US-ASCII . Votre exemple de code suppose un octet par caractère, qui fonctionne pour le test comme contenu mais pas pour Ï„Îστ comme contenu.

En tant que programmeur, vous devez savoir auprès de l'éditeur de votre fichier de données quel encodage a été utilisé pour écrire les données représentant le texte d'origine. Il est généralement préférable d'utiliser le codage UTF-8 lors de l'écriture de texte.

Écrivez un fichier texte avec deux lignes:

test Ï „Îστ

- et enregistrez en utilisant un éditeur de texte avec un encodage en UTF-8 .

Lisez ce fichier comme une collection d'objets String .

line = test
codePoint = 116
codePoint = 101
codePoint = 115
codePoint = 116
line = τέστ
codePoint = 964
codePoint = 941
codePoint = 963
codePoint = 964

Lors de l'exécution:

Path path = Paths.get( "/Users/basilbourque/some_text.txt" );
try
{
    List < String > lines = Files.readAllLines( path , StandardCharsets.UTF_8 );
    for ( String line : lines )
    {
        System.out.println( "line = " + line );
        List < Integer > codePoints = line.codePoints().boxed().collect( Collectors.toList() );
        for ( Integer codePoint : codePoints )
        {
            System.out.println( "codePoint = " + codePoint );
        }
    }
}
catch ( IOException e )
{
    e.printStackTrace();
}

UTF-16 contre UTF-8

Tu as dit:

Je pense que parce que UTF nous avons 2 octets par caractère)

Rien de tel que «UTF ».

Le contenu Ï„Îστ tel que Ï„Îστ peut être écrit dans un fichier en utilisant soit l'encodage, UTF-16 ou UTF-8. Sachez que l' UTF-16 est «considéré comme nocif» et que l'UTF-8 est généralement préféré de nos jours. Notez que UTF-8 est un sur-ensemble de US-ASCII, donc tout fichier US-ASCII est également un fichier UTF-8.

Caractères comme points de code

Si vous souhaitez donner un exemple à chaque caractère du texte, traitez-les comme des numéros de point de code .

N'utilisez jamais le type char en Java. Ce type ne peut même pas représenter la moitié des caractères définis en Unicode et est désormais obsolète.

Nous pouvons interroger chaque caractère de notre fichier exemple vu ci-dessus en ajoutant ces deux lignes de code.

line = test
116
101
115
116
line = τέστ
964
941
963
964

Comme ça:

Path path = Paths.get( "/Users/basilbourque/some_text.txt" );
try
{
    List < String > lines = Files.readAllLines( path , StandardCharsets.UTF_8 );
    for ( String line : lines )
    {
        System.out.println( "line = " + line );
        IntStream codePoints = line.codePoints();
        codePoints.forEach( System.out :: println );
    }
}
catch ( IOException e )
{
    e.printStackTrace();
}

Lors de l'exécution:

IntStream codePoints = line.codePoints();
codePoints.forEach( System.out :: println );

Si vous n'êtes pas encore familiarisé avec les flux, convertissez IntStream en une collection , telle qu'une List d'objets Integer .

line = test
line = τέστ

Lors de l'exécution:

Path path = Paths.get( "/Users/basilbourque/some_text.txt" );
try
{
    List < String > lines = Files.readAllLines( path , StandardCharsets.UTF_8 );
    for ( String line : lines )
    {
        System.out.println( "line = " + line );
    }
}
catch ( IOException e )
{
    e.printStackTrace();
}

Étant donné un numéro de point de code, nous pouvons déterminer le caractère voulu .

Chaîne s = Character.toString (941); // Caractère Î.

Sachez que certains caractères textuels peuvent être représentés comme plusieurs points de code, comme une lettre avec un signe diacritique. (La gestion de texte n'est pas une question simple.)


0 commentaires