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.


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

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

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;

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


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


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

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

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


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

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.


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

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

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

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

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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.