大きなファイルを1行ずつ読み取る方法は?


469

ファイルを1行ずつ読みたいのですが、メモリに完全にロードしていません。

ファイルが大きすぎてメモリで開くことができません。そうしようとすると、常にメモリ不足エラーが発生します。

ファイルサイズは1 GBです。



7
パラメータfgets()なしで使用する必要があり$lengthます。
Carlos

26
次のいずれかで回答としてマークしますか?
キムスタック14

回答:


684

fgets()関数を使用して、ファイルを1行ずつ読み取ることができます。

$handle = fopen("inputfile.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        // process the line read.
    }

    fclose($handle);
} else {
    // error opening the file.
} 

3
これはtoo large to open in memory部品をどのように説明しますか?
Starx

64
メモリ内のファイル全体を読み取っていません。これを実行するために必要な最大メモリは、入力の最も長い行によって異なります。
codaddict

13
@Brandin-Moot-これらの状況では、LINE BY LINEファイルを読み取るという質問では、明確な結果が得られません。
ToolmakerSteve 2016年

3
@ToolmakerSteve次に、何が起こるかを定義します。必要な場合は、「行が長すぎます;あきらめてください」というメッセージを印刷できます。これも明確な結果です。
ブランディン、2016年

2
行にブール値のfalseを含めることはできますか?その場合、このメソッドはファイルの終わりに到達せずに停止します。このURLの例1のphp.net/manual/en/function.fgets.phpは、ファイルの終わりにまだ到達していない場合でも、fgetsがブール値のfalseを返す場合があることを示しています。そのページのコメントセクションでは、fgets()が常に正しい値を返すとは限らないため、ループ条件としてfeofを使用する方が安全であると報告されています。
cjohansson 2016

130
if ($file = fopen("file.txt", "r")) {
    while(!feof($file)) {
        $line = fgets($file);
        # do same stuff with the $line
    }
    fclose($file);
}

8
@ Cuse70が彼の答えで言ったように、ファイルが存在しないか、開けない場合、これは無限ループにつながります。if($file)before whileループのテスト
FrancescoMM

10
古いことは知っていますが、while(!feof($ file))の使用はお勧めしません。こちらをご覧ください。
Kevin Van Ryckegem 2015年

ところで、「ファイルポインタに読み取るデータがなくなった場合は、FALSEが返されます。」php.net/manual/en/function.fgets.php ...念のため
エブリマン

2
feof()もう存在しませんか?
Ryan DuVal

94

オブジェクト指向のインターフェイスクラスをファイルに使用できます-SplFileObject http://php.net/manual/en/splfileobject.fgets.php(PHP 5> = 5.1.0)

<?php

$file = new SplFileObject("file.txt");

// Loop until we reach the end of the file.
while (!$file->eof()) {
    // Echo one line from the file.
    echo $file->fgets();
}

// Unset the file to call __destruct(), closing the file handle.
$file = null;

3
はるかにクリーンなソリューション。感謝;)このクラスをまだ使用していないため、ここにはさらに興味深い関数があります。php.net
Lukas Liesis

6
ありがとう。はい、たとえば、前にこの行を追加できます$ file-> setFlags(SplFileObject :: DROP_NEW_LINE); 行の終わりに改行をドロップするため。
elshnkhll 2015年

私が見る限りeof()、SplFileObjectに関数はありませんか?
Chud37 2017年

3
ありがとう!また、必要rtrim($file->fgets())に応じて、読み取られる各行ストリングの末尾の改行を取り除くために使用します。
racl101

@ Chud37はい、あります。php.net/ manual / en / splfileobject.eof.php
Nathan F.

59

大きなファイルを開いている場合、fgets()と一緒にジェネレータを使用して、ファイル全体をメモリにロードしないようにする必要があります。

/**
 * @return Generator
 */
$fileData = function() {
    $file = fopen(__DIR__ . '/file.txt', 'r');

    if (!$file)
        die('file does not exist or cannot be opened');

    while (($line = fgets($file)) !== false) {
        yield $line;
    }

    fclose($file);
};

次のように使用します。

foreach ($fileData() as $line) {
    // $line contains current line
}

このようにして、foreach()内の個々のファイル行を処理できます。

注:ジェネレーターにはPHP 5.5以上が必要です


3
代わりに、これは受け入れられる答えになるはずです。ジェネレーターを使用すると、100倍高速になります。
タチ

1
そして、メモリ効率が向上します。
NinoŠkopac18年

2
@NinoŠkopac:このソリューションの方がメモリ効率が高い理由を説明できますか?たとえば、SplFileObjectアプローチと比較して。
k00ni

30

バッファリング手法を使用してファイルを読み取ります。

$filename = "test.txt";
$source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
while (!feof($source_file)) {
    $buffer = fread($source_file, 4096);  // use a buffer of 4KB
    $buffer = str_replace($old,$new,$buffer);
    ///
}

2
巨大なファイルでも、キャリッジリターンや非常に長い行がないファイルでも機能するので、これはもっと愛に値します...
Jimmery

OPが実際の回線を気にせず、単にダウンロードなどのサービスを提供したいと思ったとしても、私は驚かないでしょう。その場合、この答えは問題ありません(そして、ほとんどのPHPプログラマーがとにかく行うことです)。
アルバロゴンサレス

30

file()ファイルに含まれている行の配列を返す関数があります。

foreach(file('myfile.txt') as $line) {
   echo $line. "\n";
}

28
1 GBのファイルはすべてメモリに読み込まれ、複数のGBアレイに変換されます。
FrancescoMM

4
これは尋ねられた質問への回答ではありませんでしたが、ここを見るときに多くの人々が持っているより一般的な質問に答えるので、それはまだ役に立ちました、ありがとう。
pilavdzice

2
file()は小さなファイルを扱うのにとても便利です。特に、最終結果としてarray()が必要な場合。
functionvoid

ファイル全体が一度にアレイに読み込まれるため、これは大きなファイルの場合の悪い考えです
Flash Thunder

これは大きなファイルではうまく機能しないため、機能しないのはまさにこの方法です。
ftrotter 2018


17

すべての回答に明らかな答えはありませんでした。
PHPには、まさにその目的のために作られた、きちんとしたストリーミング区切りパーサーがあります。

$fp = fopen("/path/to/the/file", "r+");
while ($line = stream_get_line($fp, 1024 * 1024, "\n")) {
  echo $line;
}
fclose($fp);

このコードは、最初の空の行が発生するまで行のみを返すことに注意してください。$ line!== false条件でテストする必要がありますwhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe

8

'while(!feof ... fgets()'のものに注意してください。fgetsはエラー(returnfing false)を受け取り、ファイルの終わりに到達せずに永久にループする可能性があります。codaddictは正しいのに最も近いですが、ループが終了したら、feofを確認します。trueでない場合は、エラーが発生しています。


8

これは非常に大きなファイルで管理する方法です(最大100Gでテスト済み)。そして、それはfgets()よりも高速です

$block =1024*1024;//1MB or counld be any higher than HDD block_size*2
if ($fh = fopen("file.txt", "r")) { 
    $left='';
    while (!feof($fh)) {// read the file
       $temp = fread($fh, $block);  
       $fgetslines = explode("\n",$temp);
       $fgetslines[0]=$left.$fgetslines[0];
       if(!feof($fh) )$left = array_pop($lines);           
       foreach ($fgetslines as $k => $line) {
           //do smth with $line
        }
     }
}
fclose($fh);

1024 * 1024ブロックが行の途中で壊れないようにするにはどうすればよいですか?
user151496 2018

1
@ user151496簡単!! カウント... 1.2.3.4
Omar El Don

@OmarElDonどういう意味ですか?
Codex73

7

この質問に対する一般的な解決策の1つは、改行文字に関する問題です。シンプルでかなり簡単に修正できますstr_replace

$handle = fopen("some_file.txt", "r");
if ($handle) {
    while (($line = fgets($handle)) !== false) {
        $line = str_replace("\n", "", $line);
    }
    fclose($handle);
}

6

SplFileObjectは、大きなファイルを処理する場合に役立ちます。

function parse_file($filename)
{
    try {
        $file = new SplFileObject($filename);
    } catch (LogicException $exception) {
        die('SplFileObject : '.$exception->getMessage());
    }
    while ($file->valid()) {
        $line = $file->fgets();
        //do something with $line
    }

    //don't forget to free the file handle.
    $file = null;
}

1
<?php
echo '<meta charset="utf-8">';

$k= 1;
$f= 1;
$fp = fopen("texttranslate.txt", "r");
while(!feof($fp)) {
    $contents = '';
    for($i=1;$i<=1500;$i++){
        echo $k.' -- '. fgets($fp) .'<br>';$k++;
        $contents .= fgets($fp);
    }
    echo '<hr>';
    file_put_contents('Split/new_file_'.$f.'.txt', $contents);$f++;
}
?>

-8

配列を返す関数

function read_file($filename = ''){
    $buffer = array();
    $source_file = fopen( $filename, "r" ) or die("Couldn't open $filename");
    while (!feof($source_file)) {
        $buffer[] = fread($source_file, 4096);  // use a buffer of 4KB
    }
    return $buffer;
}

4
これにより、メモリ内に1 GBを超える単一の配列が作成され(幸運を祈ります)、行ごとではなく、任意の4096文字のチャンクに分割されます。一体どうしてそんなことしたいの?
FrancescoMM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.