Como ler um arquivo grande, linha por linha?

469

Quero ler um arquivo linha por linha, mas sem carregá-lo completamente na memória.

Meu arquivo é muito grande para abrir na memória e, se tentar fazer isso, sempre fico com erros de memória.

O tamanho do arquivo é 1 GB.

adnan masood
fonte
veja a minha resposta a esta ligação
Sohail Ahmed
7
Você deve usar fgets()sem $lengthparâmetro.
Carlos
26
Deseja marcar como resposta em qualquer um dos seguintes?
perfil completo de Kim Stacks

Respostas:

684

Você pode usar a fgets()função para ler o arquivo linha por linha:

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

    fclose($handle);
} else {
    // error opening the file.
} 
codaddict
fonte
3
Como isso explica a too large to open in memorypeça?
Starx
64
Você não está lendo o arquivo inteiro na memória. A memória máxima necessária para executar isso depende da linha mais longa da entrada.
Codaddict
13
@Brandin - Moot - Nessas situações, a pergunta feita, que é ler um arquivo LINE BY LINE, não tem um resultado bem definido.
precisa
3
@ToolmakerSteve Em seguida, defina o que deve acontecer. Se quiser, basta imprimir a mensagem "Linha muito longa; desistir". e esse também é um resultado bem definido.
Brandin
2
Uma linha pode conter um falso booleano? Nesse caso, esse método seria interrompido sem chegar ao final do arquivo. O Exemplo 1 deste URL php.net/manual/en/function.fgets.php sugere que o fgets às vezes pode retornar boolean false, mesmo que o final do arquivo ainda não tenha sido atingido. Na seção de comentários dessa página, as pessoas relatam que fgets () nem sempre retorna valores corretos; portanto, é mais seguro usar feof como condicional do loop.
Cjohansson
130
if ($file = fopen("file.txt", "r")) {
    while(!feof($file)) {
        $line = fgets($file);
        # do same stuff with the $line
    }
    fclose($file);
}
Syuaa SE
fonte
8
Como @ Cuse70 disse em sua resposta, isso levará a um loop infinito se o arquivo não existir ou não puder ser aberto. Teste para if($file)o loop while #
FrancescoMM
10
Eu sei que isso é antigo, mas: usar while (! Feof ($ file)) não é recomendado. Dê uma olhada aqui.
Kevin Van Ryckegem
BTW: "Se não houver mais dados para ler no ponteiro do arquivo, FALSE será retornado." php.net/manual/en/function.fgets.php ... Apenas no caso
everyman
2
feof()não existe mais?
Ryan DuVal
94

Você pode usar uma classe de interface orientada a objetos para um arquivo - 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;
elshnkhll
fonte
3
solução muito mais limpa. Graças;) não usei esta classe, no entanto, há funções mais interessantes aqui para explorar: php.net/manual/en/class.splfileobject.php
Lukas Liesis
6
Obrigado. Sim, por exemplo, você pode adicionar essa linha antes enquanto $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); para descartar novas linhas no final de uma linha.
precisa saber é o seguinte
Tanto quanto eu posso ver, não há eof()função no SplFileObject?
precisa saber é o seguinte
3
Obrigado! Além disso, use rtrim($file->fgets())para remover as novas linhas à direita de cada sequência de linhas lida, se você não as desejar.
racl101
@ Chud37 sim, há: php.net/manual/en/splfileobject.eof.php
Nathan F.
59

Se você estiver abrindo um arquivo grande, provavelmente desejará usar Geradores ao lado de fgets () para evitar carregar o arquivo inteiro na memória:

/**
 * @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);
};

Use-o assim:

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

Dessa forma, você pode processar linhas de arquivos individuais dentro do foreach ().

Nota: Os geradores requerem> = PHP 5.5

Nino Škopac
fonte
3
Esta deve ser uma resposta aceita. É cem vezes mais rápido com geradores.
Tachi
1
E muito mais eficiente em termos de memória.
Nino Škopac
2
@ NinoŠkopac: Você pode explicar por que essa solução é mais eficiente em termos de memória? Por exemplo, em comparação com a SplFileObjectabordagem.
k00ni 24/04
30

Use técnicas de buffer para ler o arquivo.

$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);
    ///
}
Starx
fonte
2
isso merece mais amor, como ele vai trabalhar com arquivos enormes, até mesmo arquivos que não têm retornos de carro ou excessivamente longas filas ...
Jimmery
Eu não ficaria surpreso se o OP realmente não se importasse com as linhas reais e apenas quisesse, por exemplo, servir um download. Nesse caso, esta resposta é ótima (e o que a maioria dos codificadores PHP faria de qualquer maneira).
Álvaro González
30

Há uma file()função que retorna uma matriz das linhas contidas no arquivo.

foreach(file('myfile.txt') as $line) {
   echo $line. "\n";
}
NoImaginationGuy
fonte
28
O arquivo de um GB seria todo lido na memória e convertido em mais de um array de GB ... boa sorte.
FrancescoMM
4
Esta não foi a resposta para a pergunta, mas responde à pergunta mais comum que muitas pessoas têm quando consultam aqui, por isso ainda foi útil, obrigado.
precisa saber é o seguinte
2
file () é muito conveniente para trabalhar com arquivos pequenos. Especialmente quando você deseja um array () como resultado final.
functionvoid
essa é uma péssima idéia para arquivos maiores, pois todo o arquivo está sendo lido para uma matriz de uma só vez
o Flash Trovão
Isso quebra muito em arquivos grandes, portanto é exatamente o método que não funciona.
Ftrotter
19
foreach (new SplFileObject(__FILE__) as $line) {
    echo $line;
}
Quolonel Questions
fonte
Tem que amar os Oneliners
Nino Škopac
1
Onestatementers.
Perguntas Quolonel
1
Memória eficiente em comparação com file().
Nobu
17

A resposta óbvia não estava presente em todas as respostas.
O PHP possui um analisador de delimitador de fluxo disponível, criado exatamente para esse fim.

$fp = fopen("/path/to/the/file", "r+");
while ($line = stream_get_line($fp, 1024 * 1024, "\n")) {
  echo $line;
}
fclose($fp);
John
fonte
Deve-se notar que esse código retornará apenas linhas até que a primeira linha vazia ocorra. Você precisa testar $ line! == false na condição whilewhile (($line = stream_get_line($fp, 1024 * 1024, "\n")) !== false)
cebe 05/06
8

Tenha cuidado com o material 'while (! Feof ... fgets ()', o fgets pode obter um erro (returnfing false) e fazer um loop para sempre sem chegar ao final do arquivo. Codaddict estava mais próximo de estar correto, mas quando o seu 'while fgets' loop termina, verifique feof; se não for verdade, você teve um erro.

Cuse70
fonte
8

É assim que eu gerencio com arquivos muito grandes (testados com até 100G). E é mais rápido que 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);
Metodi Darzev
fonte
como você garante que o bloco 1024 * 1024 não quebre no meio da linha?
user151496
1
@ user151496 easy !! conte ... 1.2.3.4
Omar El Don
@OmarElDon, o que você quer dizer?
Codex73
7

Uma das soluções populares para essa pergunta terá problemas com o novo caractere de linha. Pode ser corrigido com bastante facilidade com um simples str_replace.

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

O SplFileObject é útil quando se trata de lidar com arquivos grandes.

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;
}
xanadev
fonte
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++;
}
?>
Nguyễn Văn Cường
fonte
-8

Função para ler com retorno de matriz

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;
}
sixvel.com
fonte
4
Isso criaria uma única matriz de mais de um GB na memória (boa sorte com ela) dividida nem mesmo em linhas, mas em pedaços arbitrários de 4096 caracteres. Por que diabos você quer fazer isso?
31915 FrancescoMM