13

Cでファイルを逆方向​​に読み取るための最良の方法は何ですか?最初はこれはまったく役に立たないと思われるかもしれませんが、ほとんどのログなどはファイルの最後に最新のデータを追加します。ファイルからテキストを逆方向に読み、行にバッファリングしたい-つまり

abc
def
ghi

ghidefabcを行で読み取る必要があります。

これまでに試しました:

    #include <stdio.h>
    #include <stdlib.h>

    void read_file(FILE *fileptr)
    {
        char currentchar = '\0';
        int size = 0;

        while( currentchar != '\n' )
        {
            currentchar = fgetc(fileptr); printf("%c\n", currentchar);
            fseek(fileptr, -2, SEEK_CUR);
            if( currentchar == '\n') { fseek(fileptr, -2, SEEK_CUR); break; }
            else size++;

        }
        char buffer[size]; fread(buffer, 1, size, fileptr);
        printf("Length: %d chars\n", size);
        printf("Buffer: %s\n", buffer);


    }


    int main(int argc, char *argv[])
    {
        if( argc < 2) { printf("Usage: backwards [filename]\n"); return 1; }

        FILE *fileptr = fopen(argv[1], "rb");
        if( fileptr == NULL ) { perror("Error:"); return 1; }

        fseek(fileptr, -1, SEEK_END); /* Seek to END of the file just before EOF */
        read_file(fileptr);


        return 0;


    }

単純に1行を読み取り、それをバッファリングしようとします。私のコードがひどいことを残念に思います、私はとても混乱しています。通常はファイル全体にメモリを割り当ててからデータを読み込むことは知っていますが、絶えず変化する大きなファイルの場合は、直接読み取る方がよいと思いました(特に、ファイル内のテキストを検索する場合)。

前もって感謝します

*申し訳ありませんが、これはLinuxで使用されるため、改行はCRなしのNLです。*

4

5 に答える 5

10

入力を program にパイプするだけで済みますがtac、これはcat逆です!

http://linux.die.net/man/1/tac

于 2013-02-12T14:30:42.403 に答える
8

fseek(binaryStream, offset, SEEK_END)動作が保証されていないため、ファイルサイズを決定するためのより移植性の高い(うまくいけば)方法をお勧めします。以下のコードを参照してください。

ファイルはカーネルレベルで少なくとも最小限にバッファリングする必要があると思います(たとえば、デフォルトではファイルごとに少なくとも1つのブロックをバッファリングする)。したがって、シークは大量の余分なI / Oを発生させず、ファイルの位置を内部で進めるだけです。デフォルトのバッファリングでは不十分な場合はsetvbuf()、I/Oを高速化するために使用してみてください。

#include <limits.h>
#include <string.h>
#include <stdio.h>

/* File must be open with 'b' in the mode parameter to fopen() */
long fsize(FILE* binaryStream)
{
  long ofs, ofs2;
  int result;

  if (fseek(binaryStream, 0, SEEK_SET) != 0 ||
      fgetc(binaryStream) == EOF)
    return 0;

  ofs = 1;

  while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 &&
         (result = (fgetc(binaryStream) == EOF)) == 0 &&
         ofs <= LONG_MAX / 4 + 1)
    ofs *= 2;

  /* If the last seek failed, back up to the last successfully seekable offset */
  if (result != 0)
    ofs /= 2;

  for (ofs2 = ofs / 2; ofs2 != 0; ofs2 /= 2)
    if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 &&
        fgetc(binaryStream) != EOF)
      ofs += ofs2;

  /* Return -1 for files longer than LONG_MAX */
  if (ofs == LONG_MAX)
    return -1;

  return ofs + 1;
}

/* File must be open with 'b' in the mode parameter to fopen() */
/* Set file position to size of file before reading last line of file */
char* fgetsr(char* buf, int n, FILE* binaryStream)
{
  long fpos;
  int cpos;
  int first = 1;

  if (n <= 1 || (fpos = ftell(binaryStream)) == -1 || fpos == 0)
    return NULL;

  cpos = n - 1;
  buf[cpos] = '\0';

  for (;;)
  {
    int c;

    if (fseek(binaryStream, --fpos, SEEK_SET) != 0 ||
        (c = fgetc(binaryStream)) == EOF)
      return NULL;

    if (c == '\n' && first == 0) /* accept at most one '\n' */
      break;
    first = 0;

    if (c != '\r') /* ignore DOS/Windows '\r' */
    {
      unsigned char ch = c;
      if (cpos == 0)
      {
        memmove(buf + 1, buf, n - 2);
        ++cpos;
      }
      memcpy(buf + --cpos, &ch, 1);
    }

    if (fpos == 0)
    {
      fseek(binaryStream, 0, SEEK_SET);
      break;
    }
  }

  memmove(buf, buf + cpos, n - cpos);

  return buf;
}

int main(int argc, char* argv[])
{
  FILE* f;
  long sz;

  if (argc < 2)
  {
    printf("filename parameter required\n");
    return -1;
  }

  if ((f = fopen(argv[1], "rb")) == NULL)
  {
    printf("failed to open file \'%s\'\n", argv[1]);
    return -1;
  }

  sz = fsize(f);
//  printf("file size: %ld\n", sz);

  if (sz > 0)
  {
    char buf[256];
    fseek(f, sz, SEEK_SET);
    while (fgetsr(buf, sizeof(buf), f) != NULL)
      printf("%s", buf);
  }

  fclose(f);
  return 0;
}

私はこれを2つの異なるコンパイラを備えたウィンドウでのみテストしました。

于 2013-02-12T18:16:04.487 に答える
4

これを行う方法はいくつかありますが、一度に 1 バイトずつ読み取るのは、間違いなく悪い選択の 1 つです。

最後の、たとえば 4KB を読み取ってから、最後の文字から前の改行まで戻るのが私の選択です。

もう 1 つのオプションはmmap、ファイルに対するもので、ファイルがメモリの塊であると仮定して、その中を逆方向にスキャンします。[mmapデータをプリフェッチするために、逆方向にも読んでいることがわかります]。

ファイルが非常に大きい (数ギガバイト) 場合は、ファイルのごく一部のみをmmap.

于 2013-02-12T14:33:31.463 に答える
1

その方法を知りたい場合は、Debian/Ubuntu の例を次に示します (RPM ベースのディストリビューションの場合は、必要に応じて変更してください)。

~$ which tac
/usr/bin/tac
~$ dpkg -S /usr/bin/tac
coreutils: /usr/bin/tac
~$ mkdir srcs
~$ cd srcs
~/srcs$ apt-get source coreutils

(apt-get の出力をクリップ)

~/srcs$ ls
coreutils-8.13  coreutils_8.13-3.2ubuntu2.1.diff.gz  coreutils_8.13-3.2ubuntu2.1.dsc  coreutils_8.13.orig.tar.gz
~/srcs$ cd coreutils-8.13/
~/srcs/coreutils-8.13$ find . -name tac.c
./src/tac.c
~/srcs/coreutils-8.13$ less src/tac.c

これは長すぎず、600 行を少し超えています。いくつかの高度な機能が含まれており、他のソースの関数を使用していますが、逆行バッファリングの実装はそのtac.cソース ファイルにあるようです。

于 2013-02-12T15:15:05.340 に答える
0

すべてのバイトの FSEEKing は、非常に遅く聞こえます。

メモリがある場合は、ファイル全体をメモリに読み込んで、逆にするか、逆方向にスキャンします。

もう 1 つのオプションは、Windows メモリ マップ ファイルです。

于 2013-02-12T14:34:11.240 に答える