<?php
namespace BcremerLineReader;
final class LineReader
{
/**
* Prevent instantiation
*/
private function __construct() {}
/**
* @param string $filePath
* @return Generator
* @throws InvalidArgumentException if $filePath is not readable
*/
public static function readLines(string $filePath): Generator
{
if (!$fh = @fopen($filePath, 'r')) {
throw new InvalidArgumentException('Cannot open file for reading: ' . $filePath);
}
return self::read($fh);
}
/**
* @param string $filePath
* @return Generator
* @throws InvalidArgumentException if $filePath is not readable
*/
public static function readLinesBackwards(string $filePath): Generator
{
if (!$fh = @fopen($filePath, 'r')) {
throw new InvalidArgumentException('Cannot open file for reading: ' . $filePath);
}
$size = filesize($filePath);
return self::readBackwards($fh, $size);
}
/**
* @param resource $fh
* @return Generator
*/
private static function read($fh): Generator
{
while (false !== $line = fgets($fh)) {
yield rtrim($line, "
");
}
fclose($fh);
}
/**
* Read a file from the end using a buffer.
*
* This is way more efficient than using the naive method
* of reading the file backwards byte by byte looking for
* a newline character.
*
* @see http://stackoverflow.com/a/10494801/147634
* @param resource $fh
* @param int $pos
* @return Generator
*/
private static function readBackwards($fh, int $pos): Generator
{
$buffer = null;
$bufferSize = 4096;
if ($pos === 0) {
return;
}
while (true) {
if (isset($buffer[1])) { // faster than count($buffer) > 1
yield array_pop($buffer);
continue;
}
if ($pos === 0) {
yield array_pop($buffer);
break;
}
if ($bufferSize > $pos) {
$bufferSize = $pos;
$pos = 0;
} else {
$pos -= $bufferSize;
}
fseek($fh, $pos);
$chunk = fread($fh, $bufferSize);
if ($buffer === null) {
// remove single trailing newline, rtrim cannot be used here
if (substr($chunk, -1) === "
") {
$chunk = substr($chunk, 0, -1);
}
$buffer = explode("
", $chunk);
} else {
$buffer = explode("
", $chunk . $buffer[0]);
}
}
}
}
github参考
如何读取大文件