web-dev-qa-db-fra.com

Java: Lire les n dernières lignes d'un fichier énorme

Je souhaite lire les n dernières lignes d’un très gros fichier sans lire le fichier dans son intégralité, que ce soit dans une zone tampon ou une zone mémoire utilisant Java.

J'ai jeté un coup d'œil aux API JDK et aux entrées-sorties Apache Commons et je ne parviens pas à en trouver une qui convienne à cette fin.

Je pensais à la façon dont la queue ou moins le fait sous UNIX. Je ne pense pas qu'ils chargent le fichier entier et ensuite montrent les dernières lignes du fichier. Il devrait y avoir une manière similaire de faire la même chose en Java aussi.

33
Gaurav Verma

Si vous utilisez un RandomAccessFile , vous pouvez utiliser length et seek pour accéder à un point spécifique vers la fin du fichier, puis lire à partir de là.

Si vous trouvez qu'il n'y a pas assez de lignes, revenez sur cette page et essayez à nouveau. Une fois que vous avez déterminé où commence la Nème dernière ligne, vous pouvez y aller et simplement lire et imprimer.

Une hypothèse de meilleure hypothèse initiale peut être faite sur la base des propriétés de vos données. Par exemple, s'il s'agit d'un fichier texte, il est possible que la longueur des lignes n'excède pas 132 en moyenne. Pour obtenir les cinq dernières lignes, commencez par 660 caractères avant la fin. Ensuite, si vous vous êtes trompé, réessayez à 13 h 20 (vous pouvez même utiliser ce que vous avez appris des 660 derniers caractères pour ajuster cela - exemple: si ces 660 caractères ne comportaient que trois lignes, la prochaine tentative pourrait être 660/3 * 5, plus peut-être un peu supplémentaire au cas où).

27
paxdiablo

J'ai trouvé la méthode la plus simple en utilisant ReversedLinesFileReader from Apache commons-io api . Cette méthode vous donnera la ligne de bas en haut d'un fichier et vous pouvez spécifier la valeur n_lines spécifiez le nombre de lignes. 

import org.Apache.commons.io.input.ReversedLinesFileReader;


File file = new File("D:\\file_name.xml");
int n_lines = 10;
int counter = 0; 
ReversedLinesFileReader object = new ReversedLinesFileReader(file);
while(counter < n_lines) {
    System.out.println(object.readLine());
    counter++;
}
25
akki_java

RandomAccessFile est un bon endroit pour commencer, comme décrit dans les autres réponses. Il y a cependant une mise en garde importante.

Si votre fichier n'est pas codé avec un codage d'un octet par caractère, la méthode readLine() ne fonctionnera pas pour vous. Et readUTF() ne fonctionnera en aucun cas. (Il lit une chaîne précédée d'un nombre de caractères ...)

Au lieu de cela, vous devrez vous assurer que vous recherchez des marqueurs de fin de ligne d'une manière qui respecte les limites de caractères du codage. Pour les codages de longueur fixe (par exemple, les variantes UTF-16 ou UTF-32), vous devez extraire des caractères à partir de positions d’octet qui sont divisibles par la taille en octets. Pour les codages à longueur variable (par exemple, UTF-8), vous devez rechercher un octet qui doit être le premier octet d'un caractère. 

Dans le cas de UTF-8, le premier octet d'un caractère sera 0xxxxxxx ou 110xxxxx ou 1110xxxx ou 11110xxx. Tout le reste est soit un deuxième/troisième octet, soit une séquence illégale UTF-8. Voir Le standard Unicode, version 5.2, chapitre 3.9 , Tableau 3-7. Cela signifie, comme le souligne le commentaire de la discussion, que tout octet 0x0A et 0x0D dans un flux UTF-8 correctement codé représentera un caractère LF ou CR. Ainsi, compter simplement les octets 0x0A et 0x0D est une stratégie d'implémentation valide (pour UTF-8) si l'on peut supposer que les autres types de séparateur de ligne Unicode (0x2028, 0x2029 et 0x0085) ne sont pas utilisés. Vous ne pouvez pas supposer que, alors le code serait plus compliqué.

Après avoir identifié une limite de caractère appropriée, vous pouvez simplement appeler new String(...) en passant le tableau d'octets, le décalage, le décompte et le codage, puis appeler de manière répétée String.lastIndexOf(...) pour compter les fins de ligne.

19
Stephen C

J'ai trouvé RandomAccessFile et les autres classes de Buffer Reader trop lentes pour moi. Rien ne peut être plus rapide qu'un tail -<#lines>. Donc c'était la meilleure solution pour moi.

public String getLastNLogLines(File file, int nLines) {
    StringBuilder s = new StringBuilder();
    try {
        Process p = Runtime.getRuntime().exec("tail -"+nLines+" "+file);
        Java.io.BufferedReader input = new Java.io.BufferedReader(new Java.io.InputStreamReader(p.getInputStream()));
        String line = null;
    //Here we first read the next line into the variable
    //line and then check for the EOF condition, which
    //is the return value of null
    while((line = input.readLine()) != null){
            s.append(line+'\n');
        }
    } catch (Java.io.IOException e) {
        e.printStackTrace();
    }
    return s.toString();
}
3
Luca

CircularFifoBuffer de Apache commons. réponse d'une question similaire à Comment lire les 5 dernières lignes d'un fichier .txt en Java

Notez que dans Apache Commons Collections 4, cette classe semble avoir été renommée en CircularFifoQueue

2
ruth542
    int n_lines = 1000;
    ReversedLinesFileReader object = new ReversedLinesFileReader(new File(path));
    String result="";
    for(int i=0;i<n_lines;i++){
        String line=object.readLine();
        if(line==null)
            break;
        result+=line;
    }
    return result;
1
Torsten Simon

Un RandomAccessFile permet de rechercher (http://download.Oracle.com/javase/1.4.2/docs/api/Java/io/RandomAccessFile.html). La méthode File.length renverra la taille du fichier. Le problème est de déterminer le nombre de lignes. Pour cela, vous pouvez chercher jusqu'à la fin du fichier et lire à l'envers jusqu'à ce que vous ayez atteint le bon nombre de lignes.

1
Yann Ramin

J'ai eu le même problème, mais je n'ai pas compris d'autres solutions.

J'ai utilisé ça. J'espère que c'est un code simple.

// String filePathName = (direction and file name).
File f = new File(filePathName);
long fileLength = f.length(); // Take size of file [bites].
long fileLength_toRead = 0;
if (fileLength > 2000) {
    // My file content is a table, I know one row has about e.g. 100 bites / characters. 
    // I used 1000 bites before file end to point where start read.
    // If you don't know line length, use @paxdiablo advice.
    fileLength_toRead = fileLength - 1000;
}
try (RandomAccessFile raf = new RandomAccessFile(filePathName, "r")) { // This row manage open and close file.
    raf.seek(fileLength_toRead); // File will begin read at this bite. 
    String rowInFile = raf.readLine(); // First readed line usualy is not whole, I needn't it.
    rowInFile = raf.readLine();
    while (rowInFile != null) {
        // Here I can readed lines (rowInFile) add to String[] array or ArriyList<String>.
        // Later I can work with rows from array - last row is sometimes empty, etc.
        rowInFile = raf.readLine();
    }
}
catch (IOException e) {
    //
}
1
pocket

Voici le meilleur moyen que j'ai trouvé pour le faire. Simple et assez rapide et efficace en mémoire.

public static void tail(File src, OutputStream out, int maxLines) throws FileNotFoundException, IOException {
    BufferedReader reader = new BufferedReader(new FileReader(src));
    String[] lines = new String[maxLines];
    int lastNdx = 0;
    for (String line=reader.readLine(); line != null; line=reader.readLine()) {
        if (lastNdx == lines.length) {
            lastNdx = 0;
        }
        lines[lastNdx++] = line;
    }

    OutputStreamWriter writer = new OutputStreamWriter(out);
    for (int ndx=lastNdx; ndx != lastNdx-1; ndx++) {
        if (ndx == lines.length) {
            ndx = 0;
        }
        writer.write(lines[ndx]);
        writer.write("\n");
    }

    writer.flush();
}
0
ra9r

Voici le travail pour cela.

    private static void printLastNLines(String filePath, int n) {
    File file = new File(filePath);
    StringBuilder builder = new StringBuilder();
    try {
        RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r");
        long pos = file.length() - 1;
        randomAccessFile.seek(pos);

        for (long i = pos - 1; i >= 0; i--) {
            randomAccessFile.seek(i);
            char c = (char) randomAccessFile.read();
            if (c == '\n') {
                n--;
                if (n == 0) {
                    break;
                }
            }
            builder.append(c);
        }
        builder.reverse();
        System.out.println(builder.toString());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
0
user11016