Question Lire les lignes de caractères et obtenir la position du fichier


Je lis des lignes séquentielles de personnages à partir d'un fichier texte. Le codage des caractères dans le fichier peut ne pas être à un seul octet.

À certains moments, je voudrais connaître la position du fichier au début de la ligne suivante, afin de pouvoir rouvrir le fichier plus tard et revenir à cette position. rapidement.

Des questions

Existe-t-il un moyen simple de faire les deux, de préférence en utilisant des bibliothèques Java standard?

Si non, quelle est une solution de rechange raisonnable?

Attributs d'une solution idéale

Une solution idéale permettrait de gérer plusieurs encodages de caractères. Cela inclut UTF-8, dans lequel différents caractères peuvent être représentés par des nombres d'octets différents. Une solution idéale reposerait principalement sur une bibliothèque fiable et bien prise en charge. Le plus idéal serait la bibliothèque Java standard. Le deuxième meilleur serait une bibliothèque Apache ou Google. La solution doit être évolutive. Lire le fichier entier en mémoire n'est pas une solution. Revenir à une position ne devrait pas nécessiter la lecture de tous les caractères antérieurs dans le temps linéaire.

Détails

Pour la première exigence, BufferedReader.readLine() est attrayant. Mais la mise en mémoire tampon interfère clairement avec l'obtention d'une position de fichier significative.

Moins évidemment, InputStreamReader peut également lire à l'avance, interférer avec l'obtention de la position du fichier. Du Documentation InputStreamReader:

Pour permettre la conversion efficace des octets en caractères, davantage d'octets peuvent être lus avant le flux sous-jacent que nécessaire pour satisfaire l'opération de lecture en cours.

La méthode RandomAccessFile.readLine()  lit un seul octet par caractère.

Chaque octet est converti en un caractère en prenant la valeur de l'octet pour les huit bits inférieurs du caractère et en mettant à zéro les huit bits supérieurs du caractère. Cette méthode ne prend donc pas en charge le jeu de caractères Unicode complet.


17
2018-06-03 18:11


origine


Réponses:


Si vous construisez un BufferedReader de FileReader et garder une instance de la FileReader accessible à votre code, vous devriez pouvoir obtenir la position de la ligne suivante en appelant:

fileReader.getChannel().position();

après un appel à bufferedReader.readLine().

le BufferedReader pourrait être construit avec un tampon d'entrée de taille 1 si vous souhaitez échanger des gains de performance pour la précision de positionnement.

Solution alternative Qu'est-ce qui ne va pas avec le suivi des octets vous-même:

long startingPoint = 0; // or starting position if this file has been previously processed

while (readingLines) {
    String line = bufferedReader.readLine();
    startingPoint += line.getBytes().length;
}

Cela vous donnerait le compte d'octets précis à ce que vous avez déjà traité, indépendamment du marquage ou de la mise en mémoire tampon sous-jacente. Vous devez tenir compte des fins de ligne dans votre décompte, car elles sont supprimées.


4
2018-06-08 18:32



Cette solution de contournement partielle ne concerne que les fichiers encodés en ASCII ou UTF-8 7 bits. Une réponse avec une solution générale est toujours souhaitable (tout comme la critique de cette solution de contournement).

Dans UTF-8:

  • Tous les caractères à un octet peuvent être distingués de tous les octets en caractères multi-octets. Tous les octets d'un caractère multi-octets ont un "1" dans la position haute. En particulier, les octets représentant LF et CR ne peuvent pas faire partie d'un caractère multi-octets.
  • Tous les caractères à un octet sont en ASCII 7 bits. Nous pouvons donc décoder un fichier contenant uniquement des caractères ASCII 7 bits avec un décodeur UTF-8.

Pris ensemble, ces deux points signifient que nous pouvons lire une ligne avec quelque chose qui lit des octets, plutôt que des caractères, puis décode la ligne.

Pour éviter les problèmes de mise en mémoire tampon, nous pouvons utiliser RandomAccessFile. Cette classe fournit des méthodes pour lire une ligne et obtenir / définir la position du fichier.

Voici une esquisse de code pour lire la ligne suivante en UTF-8 en utilisant RandomAccessFile.

protected static String 
readNextLineAsUTF8( RandomAccessFile in ) throws IOException {
    String rv = null;
    String lineBytes = in.readLine();
    if ( null != lineBytes ) {
        rv = new String( lineBytes.getBytes(),
            StandardCharsets.UTF_8 );
    }
    return rv;
 } 

Ensuite, la position du fichier peut être obtenue à partir du fichier RandomAccessFile immédiatement avant d'appeler cette méthode. Étant donné un RandomAccessFile référencé par in:

    long startPos = in.getFilePointer();
    String line = readNextLineAsUTF8( in );

2
2018-06-15 16:21



Le cas semble être résolu par VTD-XML, une bibliothèque capable d'analyser rapidement de gros fichiers XML:

La dernière implémentation java VTD-XML ximpleware, actuellement 2.13 http://sourceforge.net/projects/vtd-xml/files/vtd-xml/ fournit un code maintenant un décalage d'octet après chaque appel à la méthode getChar () de ses implémentations IReader.

Les implémentations IReader pour différents encodages de caractères sont disponibles dans VTDGen.java et VTDGenHuge.java

Les implémentations IReader sont fournies pour les encodages suivants

ASCII; ISO_8859_1 ISO_8859_10 ISO_8859_11 ISO_8859_12 ISO_8859_13 ISO_8859_14 ISO_8859_15 ISO_8859_16 ISO_8859_2 ISO_8859_3 ISO_8859_4 ISO_8859_5 ISO_8859_6 ISO_8859_7 ISO_8859_8 ISO_8859_9 UTF_16BE UTF_16LE UTF8;
WIN_1250 WIN_1251 WIN_1252 WIN_1253 Win_1254 WIN_1255 WIN_1256 WIN_1257 WIN_1258


2
2017-07-24 09:02



je voudrais suggerer java.io.LineNumberReader. Vous pouvez définir et obtenir le numéro de ligne et continuer ainsi à un certain index de ligne.

Comme c'est un BufferedReader il est également capable de manipuler UTF-8.


1
2018-06-08 18:44



Solution A

  1. Utilisation RandomAccessFile.readChar () ou RandomAccessFile.readByte () en boucle.
  2. Vérifiez vos caractères EOL, puis traitez cette ligne.

Le problème avec toute autre chose est que vous devez absolument vous assurer de ne jamais lire après le caractère EOL.

readChar () renvoie un carboniser pas un octet. Vous n'avez donc pas à vous soucier de la largeur des caractères.

Lit un caractère de ce fichier. Cette méthode lit deux octets du fichier, en commençant par le pointeur de fichier en cours.

[...]

Cette méthode bloque jusqu'à ce que les deux octets soient lus, la fin du flux est détectée ou une exception est levée.

En utilisant un RandomAccessFile et non un Reader, vous abandonnez la capacité de Java à décoder le jeu de caractères dans le fichier pour vous. Un BufferedReader le ferait automatiquement.

Il y a plusieurs façons de surmonter cela. L'une consiste à détecter vous-même le codage, puis à utiliser la méthode read * () correcte. L'autre façon serait d'utiliser un flux BoundedInput.

Il y en a un dans cette question Java: lecture de chaînes à partir d'un fichier à accès aléatoire avec entrée en mémoire tampon

Par exemple. https://stackoverflow.com/a/4305478/16549


1
2018-06-08 19:14



RandomAccessFile a une fonction: chercher (long pos)           Définit le décalage du pointeur de fichier, mesuré à partir du début de ce fichier, auquel la lecture ou l'écriture suivante a lieu.


1
2018-06-15 14:57



Au début, j'ai trouvé l'approche suggérée par Andy Thomas (https://stackoverflow.com/a/30850145/556460) Le plus approprié.

Mais malheureusement, je n'ai pas réussi à convertir le tableau d'octets (pris à partir de RandomAccessFile.readLine) pour corriger la chaîne dans les cas où la ligne de fichier contient des caractères non latins.

J'ai donc retravaillé l'approche en écrivant une fonction similaire à RandomAccessFile.readLine lui-même qui collecte les données d'une ligne à une chaîne, mais directement à un tableau d'octets, puis construit la chaîne souhaitée à partir du tableau d'octets. Donc, le code ci-dessous répondait complètement à mes besoins (en Kotlin).

Après avoir appelé la fonction, file.channel.position() renverra la position exacte de la ligne suivante (le cas échéant):

fun RandomAccessFile.readEncodedLine(charset: Charset = Charsets.UTF_8): String? {
    val lineBytes = ByteArrayOutputStream()
    var c = -1
    var eol = false

    while (!eol) {
        c = read()
        when (c) {
            -1, 10 -> eol = true // \n
            13     -> { // \r
                eol = true
                val cur = filePointer
                if (read() != '\n'.toInt()) {
                    seek(cur)
                }
            }
            else   -> lineBytes.write(c)
        }
    }

    return if (c == -1 && lineBytes.size() == 0)
        null
    else
        java.lang.String(lineBytes.toByteArray(), charset) as String
}

1
2018-05-29 14:33