2011-12-29 20 views
12

quiero leer archivo en dirección opuesta de un extremo a la puesta en mi archivo,¿Cómo leer el archivo de extremo a inicio (en orden inverso) en Java?

[1322110800] LOG ROTATION: DAILY 
[1322110800] LOG VERSION: 2.0 
[1322110800] CURRENT HOST STATE:arsalan.hussain;DOWN;HARD;1;CRITICAL - Host Unreachable (192.168.1.107) 
[1322110800] CURRENT HOST STATE: localhost;UP;HARD;1;PING OK - Packet loss = 0%, RTA = 0.06 ms 
[1322110800] CURRENT HOST STATE: musewerx-72c7b0;UP;HARD;1;PING OK - Packet loss = 0%, RTA = 0.27 ms 

i use el código para leer de esta manera,

String strpath="/var/nagios.log"; 
FileReader fr = new FileReader(strpath); 
BufferedReader br = new BufferedReader(fr); 
String ch; 
int time=0; 
String Conversion=""; 
do { 
    ch = br.readLine(); 
    out.print(ch+"<br/>"); 
} while (ch != null); 
fr.close(); 

yo preferiría leer en orden inverso utilizando el lector de memoria intermedia

+0

¿Cuál es el propósito de leer el archivo de principio a fin? – adatapost

+1

pregunta se contesta mismo tipo http://stackoverflow.com/questions/6011345/read-a-file-line-by-line-in-reverse-order Salida. – Zlatan

+0

¿no puedes simplemente leer el archivo y usar el método reverse() en StringBuilder? – asgs

Respuesta

5

Por lo que tengo entendido, intenta leer hacia atrás línea por línea. Supongamos que este es el archivo que intenta leer:

línea 1
line2
línea 3

y quiere escribirlo en el flujo de salida del servlet de la siguiente manera:

línea 3
line2
line1

El código siguiente puede ser útil en este caso:

List<String> tmp = new ArrayList<String>(); 

    do { 
     ch = br.readLine(); 
     tmp.add(ch); 
     out.print(ch+"<br/>"); 
    } while (ch != null); 

    for(int i=tmp.size()-1;i>=0;i--) { 
     out.print(tmp.get(i)+"<br/>"); 
    } 
+5

Recomendaría usar la colección Stack, ya que está orientado a LIFO –

2
@Test 
public void readAndPrintInReverseOrder() throws IOException { 

    String path = "src/misctests/test.txt"; 

    BufferedReader br = null; 

    try { 
     br = new BufferedReader(new FileReader(path)); 
     Stack<String> lines = new Stack<String>(); 
     String line = br.readLine(); 
     while(line != null) { 
      lines.push(line); 
      line = br.readLine(); 
     } 

     while(! lines.empty()) { 
      System.out.println(lines.pop()); 
     } 

    } finally { 
     if(br != null) { 
      try { 
       br.close(); 
      } catch(IOException e) { 
       // can't help it 
      } 
     } 
    } 
} 

Tenga en cuenta que este código lee el archivo agujero en la memoria y luego inicia la impresión de ella. Esta es la única forma en que puede hacerlo con un lector de memoria intermedia u otro lector que no sea compatible con la búsqueda. Debe tener esto en cuenta, en caso de que quiera leer un archivo de registro, ¡los archivos de registro pueden ser muy grandes!

Si quiere leer línea por línea e imprimir sobre la marcha, entonces no tiene otra alternativa que usar un lector que soporte buscando como java.io.RandomAccessFile y todo esto menos trivial.

47

Tuve el mismo problema que el descrito aquí. Quiero ver las líneas en el archivo en orden inverso, desde el final hasta el inicio (El comando unix tac lo hará).

Sin embargo, mis archivos de entrada son bastante grandes, por lo que leer todo el archivo en la memoria, como en los otros ejemplos no era realmente una opción viable para mí.

A continuación se muestra la clase que se me ocurrió, utiliza RandomAccessFile, pero no necesita ningún búfer, ya que solo conserva los punteros del archivo y funciona con los métodos estándar InputStream.

Funciona para mis casos, y archivos vacíos y algunas otras cosas que he probado. Ahora no tengo caracteres Unicode ni nada sofisticado, pero siempre que las líneas estén delimitadas por LF, e incluso si tienen un LF + CR debería funcionar.

Uso básico es:

in = new BufferedReader (new InputStreamReader (new ReverseLineInputStream(file))); 

while(true) { 
    String line = in.readLine(); 
    if (line == null) { 
     break; 
    } 
    System.out.println("X:" + line); 
} 

Aquí es la fuente principal:

package www.kosoft.util; 

import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.RandomAccessFile; 

public class ReverseLineInputStream extends InputStream { 

    RandomAccessFile in; 

    long currentLineStart = -1; 
    long currentLineEnd = -1; 
    long currentPos = -1; 
    long lastPosInFile = -1; 

    public ReverseLineInputStream(File file) throws FileNotFoundException { 
     in = new RandomAccessFile(file, "r"); 
     currentLineStart = file.length(); 
     currentLineEnd = file.length(); 
     lastPosInFile = file.length() -1; 
     currentPos = currentLineEnd; 
    } 

    public void findPrevLine() throws IOException { 

     currentLineEnd = currentLineStart; 

     // There are no more lines, since we are at the beginning of the file and no lines. 
     if (currentLineEnd == 0) { 
      currentLineEnd = -1; 
      currentLineStart = -1; 
      currentPos = -1; 
      return; 
     } 

     long filePointer = currentLineStart -1; 

     while (true) { 
      filePointer--; 

      // we are at start of file so this is the first line in the file. 
      if (filePointer < 0) { 
       break; 
      } 

      in.seek(filePointer); 
      int readByte = in.readByte(); 

      // We ignore last LF in file. search back to find the previous LF. 
      if (readByte == 0xA && filePointer != lastPosInFile) { 
       break; 
      } 
     } 
     // we want to start at pointer +1 so we are after the LF we found or at 0 the start of the file. 
     currentLineStart = filePointer + 1; 
     currentPos = currentLineStart; 
    } 

    public int read() throws IOException { 

     if (currentPos < currentLineEnd) { 
      in.seek(currentPos++); 
      int readByte = in.readByte(); 
      return readByte; 

     } 
     else if (currentPos < 0) { 
      return -1; 
     } 
     else { 
      findPrevLine(); 
      return read(); 
     } 
    } 
} 
+2

Mientras Es admirable que haya codificado esto usted mismo, quiere * alguna * forma de amortiguación; de lo contrario, el rendimiento de la lectura de bytes de uno en uno será deficiente. http://stackoverflow.com/a/31961274/14731 es probablemente una mejor forma de hacerlo. También codifiqué a mano una solución que ahora estoy obligado a descartar :) – Gili

+0

@Gili ¿dónde está esa solución? –

+0

@steveenzoleko Gone. Eliminé el código porque su rendimiento era demasiado pobre. Puede utilizar la solución anterior como alternativa si el rendimiento no es un problema. – Gili

9

El ReverseLineInputStream publicado anteriormente es exactamente lo que estaba buscando. Los archivos que estoy leyendo son grandes y no pueden almacenarse en el búfer.

Hay un par de errores:

  • del archivo no está cerrada
  • si la última línea no se termina las últimas 2 líneas se devuelven en la primera lectura.

Aquí está el código corregido:

package www.kosoft.util; 

import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.RandomAccessFile; 

public class ReverseLineInputStream extends InputStream { 

    RandomAccessFile in; 

    long currentLineStart = -1; 
    long currentLineEnd = -1; 
    long currentPos = -1; 
    long lastPosInFile = -1; 
    int lastChar = -1; 


    public ReverseLineInputStream(File file) throws FileNotFoundException { 
     in = new RandomAccessFile(file, "r"); 
     currentLineStart = file.length(); 
     currentLineEnd = file.length(); 
     lastPosInFile = file.length() -1; 
     currentPos = currentLineEnd; 

    } 

    private void findPrevLine() throws IOException { 
     if (lastChar == -1) { 
      in.seek(lastPosInFile); 
      lastChar = in.readByte(); 
     } 

     currentLineEnd = currentLineStart; 

     // There are no more lines, since we are at the beginning of the file and no lines. 
     if (currentLineEnd == 0) { 
      currentLineEnd = -1; 
      currentLineStart = -1; 
      currentPos = -1; 
      return; 
     } 

     long filePointer = currentLineStart -1; 

     while (true) { 
      filePointer--; 

      // we are at start of file so this is the first line in the file. 
      if (filePointer < 0) { 
       break; 
      } 

      in.seek(filePointer); 
      int readByte = in.readByte(); 

      // We ignore last LF in file. search back to find the previous LF. 
      if (readByte == 0xA && filePointer != lastPosInFile) { 
       break; 
      } 
     } 
     // we want to start at pointer +1 so we are after the LF we found or at 0 the start of the file. 
     currentLineStart = filePointer + 1; 
     currentPos = currentLineStart; 
    } 

    public int read() throws IOException { 

     if (currentPos < currentLineEnd) { 
      in.seek(currentPos++); 
      int readByte = in.readByte();    
      return readByte; 
     } else if (currentPos > lastPosInFile && currentLineStart < currentLineEnd) { 
      // last line in file (first returned) 
      findPrevLine(); 
      if (lastChar != '\n' && lastChar != '\r') { 
       // last line is not terminated 
       return '\n'; 
      } else { 
       return read(); 
      } 
     } else if (currentPos < 0) { 
      return -1; 
     } else { 
      findPrevLine(); 
      return read(); 
     } 
    } 

    @Override 
    public void close() throws IOException { 
     if (in != null) { 
      in.close(); 
      in = null; 
     } 
    } 
} 
11

Apache Commons IO tiene la clase ReversedLinesFileReader para esto ahora (bueno, desde la versión 2.2).

lo que el código podría ser:

String strpath="/var/nagios.log"; 
ReversedLinesFileReader fr = new ReversedLinesFileReader(new File(strpath)); 
String ch; 
int time=0; 
String Conversion=""; 
do { 
    ch = fr.readLine(); 
    out.print(ch+"<br/>"); 
} while (ch != null); 
fr.close(); 
6

El ReverseLineInputStream propuesto funciona muy lento cuando intenta leer miles de líneas. En mi PC Intel Core i7 en la unidad SSD era de aproximadamente 60k líneas en 80 segundos. Aquí está la versión optimizada inspirada con con memoria intermedia (a diferencia de la lectura de un byte a la vez en ReverseLineInputStream). El archivo de registro de 60k líneas se lee en 400 milisegundos:

public class FastReverseLineInputStream extends InputStream { 

private static final int MAX_LINE_BYTES = 1024 * 1024; 

private static final int DEFAULT_BUFFER_SIZE = 1024 * 1024; 

private RandomAccessFile in; 

private long currentFilePos; 

private int bufferSize; 
private byte[] buffer; 
private int currentBufferPos; 

private int maxLineBytes; 
private byte[] currentLine; 
private int currentLineWritePos = 0; 
private int currentLineReadPos = 0; 
private boolean lineBuffered = false; 

public ReverseLineInputStream(File file) throws IOException { 
    this(file, DEFAULT_BUFFER_SIZE, MAX_LINE_BYTES); 
} 

public ReverseLineInputStream(File file, int bufferSize, int maxLineBytes) throws IOException { 
    this.maxLineBytes = maxLineBytes; 
    in = new RandomAccessFile(file, "r"); 
    currentFilePos = file.length() - 1; 
    in.seek(currentFilePos); 
    if (in.readByte() == 0xA) { 
     currentFilePos--; 
    } 
    currentLine = new byte[maxLineBytes]; 
    currentLine[0] = 0xA; 

    this.bufferSize = bufferSize; 
    buffer = new byte[bufferSize]; 
    fillBuffer(); 
    fillLineBuffer(); 
} 

@Override 
public int read() throws IOException { 
    if (currentFilePos <= 0 && currentBufferPos < 0 && currentLineReadPos < 0) { 
     return -1; 
    } 

    if (!lineBuffered) { 
     fillLineBuffer(); 
    } 


    if (lineBuffered) { 
     if (currentLineReadPos == 0) { 
      lineBuffered = false; 
     } 
     return currentLine[currentLineReadPos--]; 
    } 
    return 0; 
} 

private void fillBuffer() throws IOException { 
    if (currentFilePos < 0) { 
     return; 
    } 

    if (currentFilePos < bufferSize) { 
     in.seek(0); 
     in.read(buffer); 
     currentBufferPos = (int) currentFilePos; 
     currentFilePos = -1; 
    } else { 
     in.seek(currentFilePos); 
     in.read(buffer); 
     currentBufferPos = bufferSize - 1; 
     currentFilePos = currentFilePos - bufferSize; 
    } 
} 

private void fillLineBuffer() throws IOException { 
    currentLineWritePos = 1; 
    while (true) { 

     // we've read all the buffer - need to fill it again 
     if (currentBufferPos < 0) { 
      fillBuffer(); 

      // nothing was buffered - we reached the beginning of a file 
      if (currentBufferPos < 0) { 
       currentLineReadPos = currentLineWritePos - 1; 
       lineBuffered = true; 
       return; 
      } 
     } 

     byte b = buffer[currentBufferPos--]; 

     // \n is found - line fully buffered 
     if (b == 0xA) { 
      currentLineReadPos = currentLineWritePos - 1; 
      lineBuffered = true; 
      break; 

      // just ignore \r for now 
     } else if (b == 0xD) { 
      continue; 
     } else { 
      if (currentLineWritePos == maxLineBytes) { 
       throw new IOException("file has a line exceeding " + maxLineBytes 
         + " bytes; use constructor to pickup bigger line buffer"); 
      } 

      // write the current line bytes in reverse order - reading from 
      // the end will produce the correct line 
      currentLine[currentLineWritePos++] = b; 
     } 
    } 
}} 
Cuestiones relacionadas