【问题标题】:Skip x last lines when reading a text file读取文本文件时跳过最后一行
【发布时间】:2011-12-14 06:14:04
【问题描述】:

我从大文件中逐行读取文本数据。
但我只需要阅读 n-x 行(不要阅读最后 x 行)。

如何在不超过 1 次读取整个文件的情况下做到这一点?
(我读了一行并立即处理它,所以我不能回去)

【问题讨论】:

  • 读取文件并删除最后 x 行。
  • 我无法修改源文件我也不想创建它的副本 (>4gb)
  • 有趣的问题,我投赞成票。但是,如果您在处理它们之前不知道总行数,恐怕您将无法轻松做到这一点。
  • 是的,我不知道总行数
  • 如果你不知道行数,你就不能不阅读整个文件。如果您立即处理该行,@Yoni 的答案似乎是最好的解决方案。

标签: java io


【解决方案1】:

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

备选方案#1

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

本文中的实现可能没有超级优化,但其背后的理论很清楚。

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

备选方案#2

这种方法很容易理解,而且非常直接。在执行期间,您将在内存中存储 N 行,其中 N 是您最后要跳过的行数。

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



备选方案#1

这听起来可能很奇怪,但绝对可行,而且我建议您这样做;首先向后阅读文件。

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

示例代码:

下面的代码将从/tmp/sample_file 中删除最后的42 行,并使用本文前面描述的方法打印其余部分。

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;
  }
}


备选方案#2

  1. 逐行读取文件
  2. 在每一个成功读取的行中,插入LinkedList&lt;String&gt; 后面的行
  3. 如果您的 LinkedList&lt;String&gt; 包含的行数多于您想跳过的行数,请删除第一个条目并进行处理
  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 (); 
  }
}

【讨论】:

  • 等待您的代码谢谢!我不知道您可以在存储位置之前从文件中读取
  • @markiz 抱歉花了这么长时间,我还在工作,忙着做其他事情。示例解决方案有点乱,对此我深表歉意,一直在我的个人计算机和与工作相关的东西之间来回运行。
  • @markiz 添加了对所用理论的进一步解释,我还使用该线程中其他帖子中讨论的理论编写了一个不同的替代解决方案。哪种解决方案更适合您取决于具体情况。
【解决方案2】:

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

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

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

关于缓冲区的实现,只要我们谈到Java的内置集合,一个简单的LinkedList就足够了。由于您将为放入其中的每一行从缓冲区中拉出一行,ArrayList 不会很好地执行数组索引的不断移动。一般来说,数组支持的缓冲区必须是循环的,以避免性能不佳。

【讨论】:

【解决方案3】:

只需阅读前面的x 行。那就是有一个x 行的队列。

【讨论】:

  • 你在说什么,我应该创建一个 n 大小的队列,在我逐行读取文件时填充它。当队列被填满时,如果它不是 EOF,我会处理它?
  • 是的,类似的。一边推,一边弹出并处理,同时还有行要读取。
  • 我认为你的意思是前面 x 行,而不是 n
  • err,是的,我的意思是n,但不是OP的n,我将其更改为x以避免混淆,谢谢:)
猜你喜欢
  • 1970-01-01
  • 2016-10-04
  • 1970-01-01
  • 1970-01-01
  • 2012-07-22
  • 1970-01-01
  • 2019-03-10
  • 1970-01-01
  • 2017-02-22
相关资源
最近更新 更多