读取文本文件时跳过x最后一行

mar*_*kiz 4 java io

我逐行从大文件中读取文本数据.
但我需要读取nx行(不要读取最后的x行).

如果不读取整个文件超过1次怎么办?
(我读线并立即处理它,所以我不能回去)

Yon*_*oni 6

您需要使用简单的预读逻辑.

x首先读取行并将它们放入缓冲区.然后,您可以一次重复读取一行,将其添加到缓冲区的末尾,并处理缓冲区中的第一行.到达时EOF,x缓冲区中有未处理的行.

更新:我注意到有关问题的评论和我自己的答案,所以只是为了澄清:我的建议在n未知的情况下有效.x当然应该知道.您需要做的就是创建一个简单的缓冲区,然后用x行填充缓冲区,然后开始处理.

关于缓冲区的实现,只要我们讨论Java的内置集合,只LinkedList需要一个简单的东西即可.因为你将为放入其中的每一行拉出缓冲区中的一行,所以ArrayList对数组索引的不断移位不会很好.一般来说,数组支持的缓冲区必须是循环的,以避免性能不佳.


Fil*_*efp 6

在这篇文章中,我将为您提供两种完全不同的方法来解决您的问题,根据您的使用情况,其中一种解决方案比另一种更适合.

备选方案#1

这种方法虽然非常复杂,但内存效率非常高,如果您要跳过大量内容,建议使用此方法,因为在处理过程中,您只会在内存中一次存储一行.

在这篇文章中实现它可能不是超级优化,但它背后的理论很清楚.

您将从向后读取文件开始,搜索N个换行符.当您成功找到文件中的某个位置后,您希望稍后停止处理,您将跳回文件的开头.

备选方案#2

这种方法易于理解,非常简单.在执行期间,您将在内存中存储N行,其中N是您最后要跳过的行数.

这些行将存储在FIFO容器中(先进先出).您将最后一条读取行附加到FIFO,然后删除并处理第一个条目.这样,您将始终处理距离文件末尾至少N个条目的行.



备选方案#1

这可能听起来很奇怪,但它绝对可行,而且我建议你这样做的方式; 首先向后阅读文件.

  1. 寻找文件的末尾
  2. 读取(并丢弃)字节(朝向文件的开头),直到找到SKIP_N换行符
  3. 保存这个位置
  4. 寻找文件的开头
  5. 读取(和处理)行,直到您到达已存储的位置

示例代码:

下面的代码将删除最后42一行,/tmp/sample_file并使用本文前面介绍的方法打印其余行.

import java.io.RandomAccessFile;
import java.io.File;

import java.lang.Math;

public class Example {
  protected static final int SKIP_N = 42;

  public static void main (String[] args)
    throws Exception
  {
    File fileHandle            = new File ("/tmp/sample_file");
    RandomAccessFile rafHandle = new RandomAccessFile (fileHandle, "r");
    String s1                  = new String ();

    long currentOffset = 0;
    long endOffset     = findEndOffset (SKIP_N, rafHandle);

    rafHandle.seek (0);

    while ((s1 = rafHandle.readLine ()) != null) {
      ;   currentOffset += s1.length () + 1; // (s1 + "\n").length
      if (currentOffset >= endOffset)
        break;

      System.out.println (s1);
    }
  }

  protected static long findEndOffset (int skipNLines, RandomAccessFile rafHandle)
    throws Exception
  {
    long currentOffset = rafHandle.length ();
    long endOffset     =  0;
    int  foundLines    =  0;

    byte [] buffer      = new byte[
      1024 > rafHandle.length () ? (int) rafHandle.length () : 1024
    ];

    while (foundLines < skipNLines && currentOffset != 0) {
      currentOffset = Math.max (currentOffset - buffer.length, 0);

      rafHandle.seek      (currentOffset);
      rafHandle.readFully (buffer);

      for (int i = buffer.length - 1; i > -1; --i) {
        if (buffer[i] == '\n') {
          ++foundLines;

          if (foundLines == skipNLines)
            endOffset = currentOffset + i - 1; // we want the end to be BEFORE the newline
        }
      }
    } 

    return endOffset;
  }
}
Run Code Online (Sandbox Code Playgroud)

备选方案#2

  1. 逐行读取您的文件
  2. 在每个成功读取的行上,插入行后面的行 LinkedList<String>
  3. 如果您LinkedList<String>包含的行多于您想要跳过的行,请删除第一个条目并进行处理
  4. 重复,直到没有更多的行要读取

示例代码

import java.io.InputStreamReader;
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.BufferedReader;

import java.util.LinkedList;

public class Example {
  protected static final int SKIP_N = 42; 

  public static void main (String[] args)
    throws Exception
  {
    String line;

    LinkedList<String> lli = new LinkedList<String> (); 

    FileInputStream   fis = new FileInputStream   ("/tmp/sample_file");
    DataInputStream   dis = new DataInputStream   (fis);
    InputStreamReader isr = new InputStreamReader (dis);
    BufferedReader    bre = new BufferedReader    (isr);

    while ((line = bre.readLine ()) != null) {
      lli.addLast (line);

      if (lli.size () > SKIP_N) {
        System.out.println (lli.removeFirst ());
      }   
    }   

    dis.close (); 
  }
}
Run Code Online (Sandbox Code Playgroud)