จะอ่านไฟล์ขนาดใหญ่ทีละบรรทัดได้อย่างไร?


469

ฉันต้องการอ่านไฟล์ทีละบรรทัด แต่ไม่โหลดอย่างสมบูรณ์ในหน่วยความจำ

ไฟล์ของฉันใหญ่เกินไปที่จะเปิดในหน่วยความจำและหากพยายามทำเช่นนั้นฉันจะได้รับข้อผิดพลาดจากหน่วยความจำเสมอ

ขนาดไฟล์คือ 1 GB


ดูคำตอบของฉันได้ที่ลิงค์
Sohail Ahmed

7
คุณควรใช้fgets()โดยไม่มี$lengthพารามิเตอร์
Carlos

26
คุณต้องการทำเครื่องหมายว่าเป็นคำตอบสำหรับสิ่งต่อไปนี้หรือไม่?
Kim Stacks

คำตอบ:


684

คุณสามารถใช้fgets()ฟังก์ชั่นเพื่ออ่านไฟล์ทีละบรรทัด:

$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

3
@ToolmakerSteve จากนั้นกำหนดสิ่งที่ควรเกิดขึ้น หากคุณต้องการคุณสามารถพิมพ์ข้อความ "Line ยาวเกินไป; และนั่นก็เป็นผลลัพธ์ที่ชัดเจนเช่นกัน
Brandin

2
บรรทัดสามารถมีบูลีนเท็จได้หรือไม่? ถ้าเป็นเช่นนั้นวิธีนี้จะหยุดโดยไม่ต้องถึงจุดสิ้นสุดของไฟล์ ตัวอย่าง # 1 บน URL นี้php.net/manual/th/function.fgets.phpแนะนำว่าบางครั้ง fgets สามารถส่งคืนบูลีนเท็จแม้ว่าจะถึงจุดสิ้นสุดไฟล์แล้วก็ตาม ในส่วนความคิดเห็นในหน้านั้นผู้คนรายงานว่า fgets () ไม่ได้ส่งคืนค่าที่ถูกต้องเสมอไปดังนั้นจึงปลอดภัยกว่าที่จะใช้ feof เป็นเงื่อนไขแบบวนซ้ำ
cjohansson

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)ก่อนวงวนในขณะที่
FrancescoMM

10
ฉันรู้ว่านี่เก่า แต่ไม่แนะนำให้ใช้ while (! feof ($ file)) ดูที่นี่
Kevin Van Ryckegem

BTW: "หากไม่มีข้อมูลที่จะอ่านในตัวชี้ไฟล์เพิ่มเติมจะมีการส่งคืน 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/manual/en/class.splfileobject.php
Lukas Liesis

6
ขอบคุณ ใช่ตัวอย่างเช่นคุณสามารถเพิ่มบรรทัดนี้ก่อนในขณะที่ $ file-> setFlags (SplFileObject :: DROP_NEW_LINE); เพื่อวางบรรทัดใหม่ที่ท้ายบรรทัด
elshnkhll

เท่าที่ฉันเห็นว่าไม่มีeof()ฟังก์ชั่นใน SplFileObject?
Chud37

3
ขอบคุณ! และใช้rtrim($file->fgets())เพื่อตัดการขึ้นบรรทัดใหม่สำหรับแต่ละสตริงบรรทัดที่อ่านถ้าคุณไม่ต้องการ
racl101


59

หากคุณกำลังเปิดไฟล์ขนาดใหญ่คุณอาจต้องการใช้ Generators ควบคู่ไปกับ 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
นี่ควรเป็นคำตอบที่ได้รับการยอมรับแทน มันเร็วกว่าร้อยเท่าเมื่อใช้กับเครื่องกำเนิดไฟฟ้า
Tachi

1
และเพิ่มประสิทธิภาพหน่วยความจำให้มากขึ้น
Nino Škopac

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 ส่วนใหญ่จะทำอะไรต่อไป)
ÁlvaroGonzález

30

มีfile()ฟังก์ชั่นที่ส่งกลับอาร์เรย์ของบรรทัดที่มีอยู่ในไฟล์

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

28
ไฟล์หนึ่ง GB จะถูกอ่านทั้งหมดในหน่วยความจำและแปลงเป็นอาเรย์มากกว่าหนึ่ง GB ... โชคดี
FrancescoMM

4
นี่ไม่ใช่คำตอบสำหรับคำถามที่ถาม แต่มันตอบคำถามที่พบบ่อยที่หลายคนมีเมื่อดูที่นี่ดังนั้นมันจึงยังมีประโยชน์ขอบคุณ
pilavdzice

2
ไฟล์ () สะดวกมากสำหรับการทำงานกับไฟล์ขนาดเล็ก โดยเฉพาะอย่างยิ่งเมื่อคุณต้องการอาร์เรย์ () เป็นผลลัพธ์สุดท้าย
functionvoid

นี่เป็นความคิดที่ดีกับไฟล์ที่ใหญ่กว่าเนื่องจากไฟล์ทั้งหมดกำลังถูกอ่านไปยังอาร์เรย์ในครั้งเดียว
Flash Thunder

สิ่งนี้จะทำลายไฟล์ขนาดใหญ่ได้ไม่ดีดังนั้นจึงเป็นวิธีการที่ไม่ได้ผล
ftrotter


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

8

ระวังสิ่งที่ 'while (! feof ... fgets ()', fgets อาจได้รับข้อผิดพลาด (returnfing false) และวนซ้ำตลอดไปโดยไม่ถึงจุดสิ้นสุดของไฟล์ codaddict นั้นใกล้เคียงกับความถูกต้องมากที่สุด แต่เมื่อคุณ 'ในขณะที่ fgets' ลูปสิ้นสุดตรวจสอบ feof หากไม่เป็นจริงแสดงว่าคุณมีข้อผิดพลาด


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 block ไม่แตกกลางบรรทัด
user151496

1
@ user151496 ง่าย !! นับ ... 1.2.3.4
Omar El Don

@OmarElDon ​​คุณหมายถึงอะไร?
Codex73

7

หนึ่งในวิธีการแก้ปัญหายอดนิยมสำหรับคำถามนี้จะมีปัญหากับอักขระบรรทัดใหม่ 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
สิ่งนี้จะสร้างอาเรย์เดียวที่มีหน่วยความจำมากกว่าหนึ่ง GB (ขอให้โชคดี) แบ่งออกไม่ได้แม้แต่ในบรรทัด ทำไมบนโลกนี้คุณต้องการทำเช่นนั้น?
FrancescoMM
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.