web-dev-qa-db-fra.com

Lire un fichier ligne par ligne dans l'ordre inverse

J'ai une application Java ee où j'utilise un servlet pour imprimer un fichier journal créé avec log4j. Lors de la lecture des fichiers journaux, vous recherchez généralement la dernière ligne de journal et donc le servlet serait beaucoup plus utile s'il a imprimé le fichier journal dans l'ordre inverse. Mon code actuel est:

    response.setContentType("text");
    PrintWriter out = response.getWriter();
    try {
        FileReader logReader = new FileReader("logfile.log");
        try {
            BufferedReader buffer = new BufferedReader(logReader);
            for (String line = buffer.readLine(); line != null; line = buffer.readLine()) {
                out.println(line);
            }
        } finally {
            logReader.close();
        }
    } finally {
        out.close();
    }

Les implémentations que j'ai trouvées sur Internet impliquent l'utilisation d'un StringBuffer et le chargement de tout le fichier avant l'impression, n'y a-t-il pas un moyen léger de code pour rechercher jusqu'à la fin du fichier et lire le contenu jusqu'au début du fichier?

28
eliocs

[ÉDITER]

Sur demande, je prépare cette réponse avec le sentiment d'un commentaire ultérieur: Si vous avez souvent besoin de ce comportement, une solution "plus appropriée" consiste probablement à déplacer vos journaux des fichiers texte vers les tables de base de données avec DBAppender (partie de log4j 2). Ensuite, vous pouvez simplement rechercher les dernières entrées.

[/ÉDITER]

J'aborderais probablement cela légèrement différemment des réponses énumérées.

(1) Créez une sous-classe de Writer qui écrit les octets codés de chaque caractère dans l'ordre inverse:

public class ReverseOutputStreamWriter extends Writer {
    private OutputStream out;
    private Charset encoding;
    public ReverseOutputStreamWriter(OutputStream out, Charset encoding) {
        this.out = out;
        this.encoding = encoding;
    }
    public void write(int ch) throws IOException {
        byte[] buffer = this.encoding.encode(String.valueOf(ch)).array();
        // write the bytes in reverse order to this.out
    }
    // other overloaded methods
}

(2) Créez une sous-classe de log4j WriterAppender dont la méthode createWriter serait remplacée pour créer une instance de ReverseOutputStreamWriter.

(3) Créez une sous-classe de log4j Layout dont la méthode format renvoie la chaîne de journal dans l'ordre inverse des caractères:

public class ReversePatternLayout extends PatternLayout {
    // constructors
    public String format(LoggingEvent event) {
        return new StringBuilder(super.format(event)).reverse().toString();
    }
}

(4) Modifier mon fichier de configuration de journalisation pour envoyer des messages de journal à les deux le fichier journal "normal" et un fichier journal "inversé". Le fichier journal "inverse" contiendrait les mêmes messages de journal que le fichier journal "normal", mais chaque message serait écrit à l'envers. (Notez que l'encodage du fichier journal "inversé" ne serait pas nécessairement conforme à UTF-8, ni même à aucun encodage de caractères.)

(5) Créez une sous-classe de InputStream qui encapsule une instance de RandomAccessFile afin de lire les octets d'un fichier dans l'ordre inverse:

public class ReverseFileInputStream extends InputStream {
    private RandomAccessFile in;
    private byte[] buffer;
    // The index of the next byte to read.
    private int bufferIndex;
    public ReverseFileInputStream(File file) {
        this.in = new RandomAccessFile(File, "r");
        this.buffer = new byte[4096];
        this.bufferIndex = this.buffer.length;
        this.in.seek(file.length());
    }
    public void populateBuffer() throws IOException {
        // record the old position
        // seek to a new, previous position
        // read from the new position to the old position into the buffer
        // reverse the buffer
    }
    public int read() throws IOException {
        if (this.bufferIndex == this.buffer.length) {
            populateBuffer();
            if (this.bufferIndex == this.buffer.length) {
                return -1;
            }
        }
        return this.buffer[this.bufferIndex++];
    }
    // other overridden methods
}

Maintenant, si je veux lire les entrées du fichier journal "normal" dans l'ordre inverse, il me suffit de créer une instance de ReverseFileInputStream, en lui donnant le fichier journal "revere".

11
Nathan Ryan

C'est une vieille question. Je voulais aussi faire la même chose et après quelques recherches, il y a une classe dans Apache commons-io pour y parvenir:

org.Apache.commons.io.input.ReversedLinesFileReader

8

Je pense qu'un bon choix pour cela serait d'utiliser la classe RandomFileAccess . Il existe un exemple de code pour la relecture en utilisant cette classe sur cette page . La lecture d'octets de cette façon est facile, mais la lecture de chaînes peut être un peu plus difficile.

4
yms

Si vous êtes pressé et que vous voulez la solution la plus simple sans trop vous soucier des performances, je voudrais essayer d'utiliser un processus externe pour faire le sale boulot (étant donné que vous exécutez votre application sur un serveur Un * x, comme tout une personne décente ferait XD)

new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("tail yourlogfile.txt -n 50 | rev").getProcess().getInputStream()))
2
fortran

Une alternative plus simple, car vous dites que vous créez une servlet pour ce faire, est d'utiliser un LinkedList pour contenir le dernier N lignes (où N peut être un paramètre de servlet). Lorsque la taille de la liste dépasse N , vous appelez removeFirst().

Du point de vue de l'expérience utilisateur, c'est probablement la meilleure solution. Comme vous le constatez, les lignes les plus récentes sont les plus importantes. Ne pas être submergé d'informations est également très important.

2
Anon

Bonne question. Je ne suis au courant d'aucune implémentation courante de cela. Ce n'est pas trivial de faire correctement non plus, alors faites attention à ce que vous choisissez. Il devrait traiter du codage des jeux de caractères et de la détection des différentes méthodes de saut de ligne. Voici l'implémentation que j'ai jusqu'à présent qui fonctionne avec les fichiers encodés ASCII et UTF-8, y compris un cas de test pour UTF-8. Cela ne fonctionne pas avec les fichiers encodés UTF-16LE ou UTF-16BE .

import Java.io.BufferedReader;
import Java.io.ByteArrayOutputStream;
import Java.io.File;
import Java.io.FileInputStream;
import Java.io.IOException;
import Java.io.InputStreamReader;
import Java.io.RandomAccessFile;
import Java.io.Reader;
import Java.io.UnsupportedEncodingException;
import Java.nio.ByteBuffer;
import Java.nio.channels.FileChannel;
import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.List;

import junit.framework.TestCase;

public class ReverseLineReader {
    private static final int BUFFER_SIZE = 8192;

    private final FileChannel channel;
    private final String encoding;
    private long filePos;
    private ByteBuffer buf;
    private int bufPos;
    private byte lastLineBreak = '\n';
    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    public ReverseLineReader(File file, String encoding) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "r");
        channel = raf.getChannel();
        filePos = raf.length();
        this.encoding = encoding;
    }

    public String readLine() throws IOException {
        while (true) {
            if (bufPos < 0) {
                if (filePos == 0) {
                    if (baos == null) {
                        return null;
                    }
                    String line = bufToString();
                    baos = null;
                    return line;
                }

                long start = Math.max(filePos - BUFFER_SIZE, 0);
                long end = filePos;
                long len = end - start;

                buf = channel.map(FileChannel.MapMode.READ_ONLY, start, len);
                bufPos = (int) len;
                filePos = start;
            }

            while (bufPos-- > 0) {
                byte c = buf.get(bufPos);
                if (c == '\r' || c == '\n') {
                    if (c != lastLineBreak) {
                        lastLineBreak = c;
                        continue;
                    }
                    lastLineBreak = c;
                    return bufToString();
                }
                baos.write(c);
            }
        }
    }

    private String bufToString() throws UnsupportedEncodingException {
        if (baos.size() == 0) {
            return "";
        }

        byte[] bytes = baos.toByteArray();
        for (int i = 0; i < bytes.length / 2; i++) {
            byte t = bytes[i];
            bytes[i] = bytes[bytes.length - i - 1];
            bytes[bytes.length - i - 1] = t;
        }

        baos.reset();

        return new String(bytes, encoding);
    }

    public static void main(String[] args) throws IOException {
        File file = new File("my.log");
        ReverseLineReader reader = new ReverseLineReader(file, "UTF-8");
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
    }

    public static class ReverseLineReaderTest extends TestCase {
        public void test() throws IOException {
            File file = new File("utf8test.log");
            String encoding = "UTF-8";

            FileInputStream fileIn = new FileInputStream(file);
            Reader fileReader = new InputStreamReader(fileIn, encoding);
            BufferedReader bufReader = new BufferedReader(fileReader);
            List<String> lines = new ArrayList<String>();
            String line;
            while ((line = bufReader.readLine()) != null) {
                lines.add(line);
            }
            Collections.reverse(lines);

            ReverseLineReader reader = new ReverseLineReader(file, encoding);
            int pos = 0;
            while ((line = reader.readLine()) != null) {
                assertEquals(lines.get(pos++), line);
            }

            assertEquals(lines.size(), pos);
        }
    }
}
1
WhiteFang34

vous pouvez utiliser RandomAccessFile implémente cette fonction, comme:

import Java.io.File;
import Java.io.IOException;
import Java.io.RandomAccessFile;

import com.google.common.io.LineProcessor;
public class FileUtils {
/**
 * 反向读取文本文件(UTF8),文本文件分行是通过\r\n
 * 
 * @param <T>
 * @param file
 * @param step 反向寻找的步长
 * @param lineprocessor
 * @throws IOException
 */
public static <T> T backWardsRead(File file, int step,
        LineProcessor<T> lineprocessor) throws IOException {
    RandomAccessFile rf = new RandomAccessFile(file, "r");
    long fileLen = rf.length();
    long pos = fileLen - step;
    // 寻找倒序的第一行:\r
    while (true) {
        if (pos < 0) {
            // 处理第一行
            rf.seek(0);
            lineprocessor.processLine(rf.readLine());
            return lineprocessor.getResult();
        }
        rf.seek(pos);
        char c = (char) rf.readByte();
        while (c != '\r') {
            c = (char) rf.readByte();
        }
        rf.readByte();//read '\n'
        pos = rf.getFilePointer();
        if (!lineprocessor.processLine(rf.readLine())) {
            return lineprocessor.getResult();
        }
        pos -= step;
    }

  }

utilisation:

       FileUtils.backWardsRead(new File("H:/usersfavs.csv"), 40,
            new LineProcessor<Void>() {
                                   //TODO  implements method
                                   .......
            });
1
user1536505
import Java.io.File;
import Java.io.IOException;
import Java.nio.charset.Charset;
import Java.nio.file.Files;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Collections;
import Java.util.Comparator;
import Java.util.HashSet;
import Java.util.List;
import Java.util.Set;
/**
 * Inside of C:\\temp\\vaquar.txt we have following content
 * vaquar khan is working into Citi He is good good programmer programmer trust me
 * @author [email protected]
 *
 */

public class ReadFileAndDisplayResultsinReverse {
    public static void main(String[] args) {
        try {
            // read data from file
            Object[] wordList = ReadFile();
            System.out.println("File data=" + wordList);
            //
            Set<String> uniquWordList = null;
            for (Object text : wordList) {
                System.out.println((String) text);
                List<String> tokens = Arrays.asList(text.toString().split("\\s+"));
                System.out.println("tokens" + tokens);
                uniquWordList = new HashSet<String>(tokens);
                // If multiple line then code into same loop
            }
            System.out.println("uniquWordList" + uniquWordList);

            Comparator<String> wordComp= new Comparator<String>() {

                @Override
                public int compare(String o1, String o2) {
                    if(o1==null && o2 ==null) return 0;
                    if(o1==null ) return o2.length()-0;
                    if(o2 ==null) return o1.length()-0;
                    //
                    return o2.length()-o1.length();
                }
            };
            List<String> fs=new ArrayList<String>(uniquWordList);
            Collections.sort(fs,wordComp);

            System.out.println("uniquWordList" + fs);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static Object[] ReadFile() throws IOException {
        List<String> list = Files.readAllLines(new File("C:\\temp\\vaquar.txt").toPath(), Charset.defaultCharset());
        return list.toArray();
    }


}

Sortie:

[Vaquar khan travaille dans Citi Il est bon bon programmeur programmeur croyez-moi jetons [vaquar, khan, est, travaille, dans, Citi, He, est, bon, bon, programmeur, programmeur, confiance, moi]

uniquWordList [trust, vaquar, programmer, is, good, into, khan, me, working, Citi, He]

uniquWordList [programmeur, travaillant, vaquar, trust, good, into, khan, Citi, is, me, He]

Si vous voulez trier A à Z, écrivez un autre comparateur

0
vaquar khan

Solution concise utilisant Java 7 Autoclosables et Java 8 Streams:

try (Stream<String> logStream = Files.lines(Paths.get("C:\\logfile.log"))) {
   logStream
      .sorted(Comparator.reverseOrder())
      .limit(10) // last 10 lines
      .forEach(System.out::println);
}

gros inconvénient: ne fonctionne que lorsque les lignes sont strictement dans l'ordre naturel, comme les fichiers journaux préfixés avec des horodatages mais sans exception

0
Journeycorner

La solution la plus simple consiste à lire le fichier dans l'ordre suivant, à l'aide d'un ArrayList<Long> pour conserver le décalage en octets de chaque enregistrement de journal. Vous devrez utiliser quelque chose comme Jakarta Commons CountingInputStream pour récupérer la position de chaque enregistrement, et devrez organiser soigneusement vos tampons pour vous assurer qu'il renvoie les valeurs appropriées:

FileInputStream fis = // .. logfile
BufferedInputStream bis = new BufferedInputStream(fis);
CountingInputStream cis = new CountingInputSteam(bis);
InputStreamReader isr = new InputStreamReader(cis, "UTF-8");

Et vous ne pourrez probablement pas utiliser un BufferedReader, car il tentera de lire à l'avance et de supprimer le décompte (mais la lecture d'un caractère à la fois ne sera pas un problème de performances, car vous ' re tamponnage plus bas dans la pile).

Pour écrire le fichier, vous parcourez la liste en arrière et utilisez un RandomAccessFile. Il y a une petite astuce: pour décoder correctement les octets (en supposant un codage multi-octets), vous devrez lire les octets correspondant à une entrée, puis lui appliquer un décodage. La liste, cependant, vous donnera la position de début et de fin des octets.

Un gros avantage de cette approche, par rapport à la simple impression des lignes dans l'ordre inverse, est que vous n'endommagerez pas les messages de journal sur plusieurs lignes (comme les exceptions).

0
Anon