PHP Conference Japan 2024

fseek

(PHP 4, PHP 5, PHP 7, PHP 8)

fseek在文件指针中定位

描述

fseek(resource $stream, int $offset, int $whence = SEEK_SET): int

设置 stream 引用的文件的文件位置指示器。新位置(从文件开头以字节数衡量)是通过将 offset 添加到 whence 指定的位置来获得的。

通常,允许定位到文件结尾之后;如果随后写入数据,则在文件结尾和定位位置之间任何未写入区域的读取将产生值为 0 的字节。但是,某些流可能不支持此行为,尤其是当它们具有底层固定大小存储时。

参数

stream

文件系统指针 resource,通常使用 fopen() 创建。

offset

偏移量。

要移动到文件结尾之前的位置,你需要在 offset 中传递一个负值,并将 whence 设置为 SEEK_END

whence

whence 的值是:

  • SEEK_SET - 设置位置等于 offset 字节。
  • SEEK_CUR - 设置位置为当前位置加上 offset
  • SEEK_END - 设置位置为文件结尾加上 offset

返回值

成功时返回 0;否则返回 -1。

范例

示例 #1 fseek() 示例

<?php

$fp
= fopen('somefile.txt', 'r');

// 读取一些数据
$data = fgets($fp, 4096);

// 移动回文件开头
// 与 rewind($fp) 相同;
fseek($fp, 0);

?>

注释

注意:

如果你以追加模式(aa+)打开文件,则无论文件位置如何,你写入文件的任何数据都将始终被追加,并且调用 fseek() 的结果将是未定义的。

注意:

并非所有流都支持定位。对于那些不支持定位的流,从当前位置向前定位是通过读取和丢弃数据来实现的;其他形式的定位将失败。

参见

  • ftell() - 返回文件读/写指针的当前位置
  • rewind() - 倒回文件指针的位置

添加注释

用户贡献的注释 25 条注释

up
83
seeker at example com
16 年前
只是为了引用和指出这一点

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
3. 如果你使用 fseek() 向文件写入数据,请记住以 "r+" 模式打开文件
例如

$fp=fopen($filename,"r+");

不要以 "a" 模式(追加)打开文件,因为它会将
文件指针放在文件末尾,并且不允许你
fseek 文件中较早的位置(对我来说不行!)。另外,
不要以 "w" 模式打开文件 -- 尽管这会将你放在
文件开头 -- 因为它会擦除文件中的所有数据。
我花了半天时间才弄明白 :/

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Took me half a day to figure :/
up
19
匿名
13 年前
官方文档指出并非所有流都是可定位的。
你可以尝试定位并处理失败

<?php
if (fseek($stream, $offset, SEEK_CUR) === -1) {
// 无论如何
}
?>

或者,你可以使用 stream_get_meta_data 函数
https://php.net/stream_get_meta_data

<?php
function fseekable($stream) {
$meta = stream_get_meta_data($stream);
return
$meta['seekable'];
}
?>
up
16
lorenzo dot stanco at gmail dot com
11 年前
我想就“从文件中读取最后几行”这个主题做出我的贡献。我做了一些研究(实际上是从这里开始的)并针对不同的算法和场景运行了许多测试,得出了这个

在 PHP 中从文件中读取最后几行的最佳方法是什么?
http://stackoverflow.com/a/15025877/995958

在那篇小文章中,我尝试分析了所有不同的方法及其在不同文件上的性能。

希望它有帮助。
up
6
marc dot roe at gmail dot com
18 年前
我尝试改进和修改 (mail at ulf-kosack dot de) 的函数。实际上它非常快,即比使用 file() 或 file_get_contents() 获取文件的最后五行、十行或其他行所需的时间要少得多。

function read_file($file, $lines)
{
$handle = fopen($file, "r");
$linecounter = $lines;
$pos = -2;
$beginning = false;
$text = array();
while ($linecounter > 0) {
$t = " ";
while ($t != "\n") {
if(fseek($handle, $pos, SEEK_END) == -1) {
$beginning = true; break; }
$t = fgetc($handle);
$pos --;
}
$linecounter --;
if($beginning) rewind($handle);
$text[$lines-$linecounter-1] = fgets($handle);
if($beginning break;
}
fclose ($handle);
return array_reverse($text); // array_reverse 是可选的:你也可以只返回由文件行组成的 $text 数组。
}

现在的好处是,当你请求的行数超过文件包含的行数时,你不会得到一个错误。在这种情况下,该函数将只返回整个文件的内容。
up
11
synnus 在 gmail 点 com
10 年前
在 Php 32 位 (X86) 中写入 4GB 虚拟文件
如果你想写入更大的 GB 文件 (>4GB),请使用 Php(X64)。
此文件在 0.0041329860687256 秒内创建

CreatFileDummy('data_test.txt',4294967296);

函数 CreatFileDummy($file_name,$size) {
// 32 位最大大小为 4 294 967 296 字节
$f = fopen($file_name, 'wb');
if($size >= 1000000000) {
$z = ($size / 1000000000);
if (is_float($z)) {
$z = round($z,0);
fseek($f, ( $size - ($z * 1000000000) -1 ), SEEK_END);
fwrite($f, "\0");
}
while(--$z > -1) {
fseek($f, 999999999, SEEK_END);
fwrite($f, "\0");
}
}
else {
fseek($f, $size - 1, SEEK_END);
fwrite($f, "\0");
}
fclose($f);

返回 true;
}

Synx
up
7
匿名用户
16 年前
致:seeker 在 example com
不过要小心。
如果你以 (r+) 模式打开文件,你可以自由定位你的指针,但它会“覆盖”数据,而不是“追加数据”。

测试了这个

<?php
// file.txt 内容:
// "你可以从舒适的浏览器中向 PHP 手册贡献你的笔记!"

$handler = fopen("file.txt", "r+");
fseek($handler, 0);
fwrite($handler, "想要添加这个");
?>
file.txt 的新内容将是这样的
"想要添加这个te 你的笔记到 PHP 手册从舒适的你的浏览器!"。

如果你真的想在开头追加,你必须首先获取所有内容,保存它,清除文件,放入新内容并在末尾追加保存的内容。
up
4
Lutz ( l_broedel 在 gmx 点 net )
19 年前
基于下面由 info 在 o08 点 com 提供的函数(谢谢),以下内容应该使你能够从文件中读取单行,由行号标识(从 1 开始)

<?
函数 readLine ($linenum,$fh) {
$line = fgets ($fh, 4096);
$pos = -1;
$i = 0;

while (!feof($fh) && $i<($linenum-1)) {
$char = fgetc($fh);
if ($char != "\n" && $char != "\r") {
fseek($fh, $pos, SEEK_SET);
$pos ++;
}
else $i ++;
}
$line = fgets($fh);
返回 $line;
} //readLine()
?>
up
3
匿名用户
22 年前
不要对可能由并行进程或线程访问和更新的文件使用 filesize()(因为 filesize() 返回值保存在缓存中)。
相反,如果你需要执行 fread() 调用来读取整个文件,则锁定打开的文件并使用 fseek($fp,0,SEEK_END) 和 ftell($fp) 来获取实际文件大小...
up
5
我 在 php 点 net
11 年前
如何使用 fseek 读取大文件(大于 2GB+,最大到任何大小,如 4GB+、100GB+、100 TB+、任何文件大小、100 PB,最大限制为 php_float_max)?

// 将文件指针查找/设置到 50 GB
my_fseek($fp, floatval(50000000000),1);

函数 my_fseek($fp,$pos,$first=0) {

// 最初设置为 0 位置,一次性
if($first) fseek($fp,0,SEEK_SET);

// 获取 pos 浮点值
$pos=floatval($pos);

// 在限制范围内,使用正常的 fseek
if($pos<=PHP_INT_MAX)
fseek($fp,$pos,SEEK_CUR);
// 超出限制,使用递归 fseek
else {
fseek($fp,PHP_INT_MAX,SEEK_CUR);
$pos -= PHP_INT_MAX;
my_fseek($fp,$pos);
}

}

希望这有帮助。
up
1
Tom Pittlik
15 年前
下面的 tail 示例函数在尝试打开大文件时将返回 PHP 内存限制错误。由于 tail 对于打开大型日志很方便,这里有一个函数可以让你(如果你有权限)

<?php

函数 unix_tail($lines,$file)
{
shell_exec("tail -n $lines $file > /tmp/phptail_$file");
$output = file_get_contents("/tmp/phptail_$file");
unlink("/tmp/phptail_$file");
返回
$output;
}

?>
up
1
kavoshgar3 在 gmail 点 com
13 年前
有时我们想从最后一行读取文件到文件开头。我使用以下内容。
<?php
函数 read_backward_line($filename, $lines, $revers = false)
{
$offset = -1;
$c = '';
$read = '';
$i = 0;
$fp = @fopen($filename, "r");
while(
$lines && fseek($fp, $offset, SEEK_END) >= 0 ) {
$c = fgetc($fp);
if(
$c == "\n" || $c == "\r"){
$lines--;
if(
$revers ){
$read[$i] = strrev($read[$i]);
$i++;
}
}
if(
$revers ) $read[$i] .= $c;
else
$read .= $c;
$offset--;
}
fclose ($fp);
if(
$revers ){
if(
$read[$i] == "\n" || $read[$i] == "\r")
array_pop($read);
else
$read[$i] = strrev($read[$i]);
返回
implode('',$read);
}
返回
strrev(rtrim($read,"\n\r"));
}
//如果 $revers=false 函数返回->
//第 1000 行:我是第 1000 行
//第 1001 行:我是第 1001 行
//第 1002 行:我是最后一行
//但如果 $revers=true 函数返回->
//第 1002 行:我是最后一行
//第 1001 行:我是第 1001 行
//第 1000 行:我是第 1000 行
?>
尽情享用!如果有效,请给我发邮件!;-)
up
0
marin 在 sagovac 点 com
8 年前
查找一行代码,然后从 while 循环中跳出以提高性能。使用 SEEK_SET 查找特定行并获取特定行。如果 $range 为 '0',则将显示查找到的行。如果设置为 '2',它将显示当前行 + 上面 2 行 + 下面 2 行。

对于在非常大的文件中获取文件内容以获取行范围很有用。为了提高性能,while 循环会从迭代中跳出,然后继续查找。

我创建了一个函数,该函数读取文件并计算行数,并将每行的字节存储到数组中以进行查找。如果设置了 `linenum` 指定的最大值,它将从 while 循环中跳出以保持性能,然后在新的循环函数中查找字节位置以获取文件内容。

函数 readFileSeek($source, $linenum = 0, $range = 0)
{
$fh = fopen($source, 'r');
$meta = stream_get_meta_data($fh);

if (!$meta['seekable']) {
throw new Exception(sprintf("源不可查找:%s", print_r($source, true)));
}

$pos = 2;
$result = null;

if ($linenum) {
$minline = $linenum - $range - 1;
$maxline = $minline+$range+$range;
}

$totalLines = 0;
while (!feof($fh)) {

$char = fgetc($fh);

if ($char == "\n" || $char == "\r") {
++$totalLines;
} else {
$result[$totalLines] = $pos;
}
$pos++;

if ($maxline+1 == $totalLines) {
// 从 while 循环中跳出以不读取整个文件
break;
}
}

$buffer = '';

for ($nr=$minline; $nr<=$maxline; $nr++) {

如果 (isset($result[$nr])) {

fseek($fh, $result[$nr], SEEK_SET);

while (!feof($fh)) {
$char = fgetc($fh);

if ($char == "\n" || $char == "\r") {
$buffer .= $char;
break;
} else {
$buffer .= $char;
}
}

}
}

返回 $buffer;
}

测试结果(1.3 GB 文件,100000000 行代码,定位到第 300000 行代码)

字符串(55) "299998_abc
299999_abc
300000_abc
300001_abc
300002_abc
"

时间:612 毫秒,内存:20.00Mb

$ ll -h /tmp/testfile
-rw-rw-r-- 1 1,3G /tmp/testfile
up
2
chenganeyou at eyou dot com
19 年前
我使用以下代码读取文件的最后一行。
与 jim at lfchosting dot com 相比,它应该更有效率。

<?php
function readlastline($file)
{
$linecontent = " ";
$contents = file($file);
$linenumber = sizeof($file)-1;
$linecontet = $contents[$linenumber];
unset(
$contents,$linenumber);
return
$linecontent;
}
?>
up
0
ben at nullcreations dot net
16 年前
更简单的 PHP tail() 函数

<?php
function tail($file, $num_to_get=10)
{
$fp = fopen($file, 'r');
$position = filesize($file);
fseek($fp, $position-1);
$chunklen = 4096;
while(
$position >= 0)
{
$position = $position - $chunklen;
if (
$position < 0) { $chunklen = abs($position); $position=0;}
fseek($fp, $position);
$data = fread($fp, $chunklen). $data;
if (
substr_count($data, "\n") >= $num_to_get + 1)
{
preg_match("!(.*?\n){".($num_to_get-1)."}$!", $data, $match);
return
$match[0];
}
}
fclose($fp);
return
$data;
}
?>
up
1
alan at peaceconstitution.com
19 年前
感谢 Dan,他上面的评论提供了一个解决如何向文件追加内容的方案。
在使用 phpinfo(); 确保我的 PHP 安装具有 fopen() 手册条目中提到的必要设置后,我很困惑为什么我使用带有追加选项 'a' 的 fopen() 不起作用。然后我阅读了附录 L (http://us2.php.net/manual/en/wrappers.php) 的一条评论,指出 fopen() 的追加选项 'a' 不按预期工作。作者建议改用 'w' 选项,我发现确实有效。但是 'w' 选项(写入选项)会覆盖文件中的所有内容。
问题仍然是如何实现追加。按照 Dan 关于 'r+' 选项的建议,我尝试了这个,它工作得很好
$string = "要写入日志的消息";
$filehandle = fopen ("/home/name/sqllogs/phpsqlerr.txt", 'r+');
fseek ( $filehandle,0, SEEK_END);
fwrite ( $filehandle, $string."\n" );
fclose ($filehandle);
up
0
ekow[at]te.ugm.ac.id
18 年前
对 chenganeyou at eyou dot com 读取最后一行的代码稍作更正。
$linenumber = sizeof($file)-1;
应该是
$linenumber = sizeof($contents)-1;
因为 sizeof 将计算数组元素,而不是文件大小。
<?php
function readlastline($file)
{
$linecontent = " ";
$contents = file($file);
$linenumber = sizeof($contents)-1;
$linecontet = $contents[$linenumber];
unset(
$contents,$linenumber);
return
$linecontent;
}
?>
up
0
phil at NOSPAM dot blisswebhosting dot com
19 年前
为了从头到尾读取文本文件,例如首先显示日志文件的最新内容。我使用以下内容。

它基本上只是使用 fseek 来查找文件的结尾,使用 ftell 来查找计数器的字节数,然后使用 fgetc 向后迭代文件以测试换行符。

$i=0 ;
$lines=500 ;
$fp = fopen($log,"r") ;
if(is_resource($fp)){
fseek($fp,0,SEEK_END) ;
$a = ftell($fp) ;
while($i <= $lines){
if(fgetc($fp) == "\n"){
echo (fgets($fp));
$i++ ;
}
fseek($fp,$a) ;
$a-- ;
}
}
up
0
jim at lfchosting dot com
21 年前
这是一个返回文件最后一行的函数。这应该比读取整个文件直到到达最后一行更快。如果你想加快速度,你可以设置 $pos = 某个大于行长的数字。我处理的文件长度不同,所以这对我有用。

<?php
function readlastline($file)
{
$fp = @fopen($file, "r");
$pos = -1;
$t = " ";
while (
$t != "\n") {
fseek($fp, $pos, SEEK_END);
$t = fgetc($fp);
$pos = $pos - 1;
}
$t = fgets($fp);
fclose($fp);
return
$t;
}
?>
up
-1
匿名
13 年前
我需要流式传输一个 txt 文件(这里是大 xml 文件)以按块获取节点。我找不到更短的方法。所以我写了这个类。

函数:流式传输完整文件并返回两个搜索字符串之间的内容及其搜索字符串(多字节安全)

希望对任何人有帮助。

PS:它缺少任何布尔检查/针对不存在的文件/读取错误的异常处理。

<?php
/**
* 分块读取 txt 文件
* 用法:
$c_streamFileTxt = new streamFileTxt;
$_args = array(

'file' => 'temporary.xml',
'start_string' => '<Product>',
'stop_string' => '</Product>',
'block_size' => '8192'
);

$c_streamFileTxt->setArgs($_args);

while ($txt_block = $c_streamFileTxt->getNextBlock())
{
// 使用 $txt_block 进行某些操作
}
*/

class streamFileTxt
{
private
$handle;
private
$file;
private
$file_offset;
private
$block;
private
$start_string;
private
$stop_string;
private
$block_size;

/**
* 设置类参数
* @param array $_args
*/
public function setArgs($_args)
{
$this->file = $_args['file'];
$this->start_string = $_args['start_string'];
$this->stop_string = $_args['stop_string'];
$this->block_size = $_args['block_size'];
}

/**
* 获取文件中的下一个文本块
* @param void
* @return string $textblock
*/
public function getNextBlock()
{
$this->openFile();

fseek($this->handle, $this->file_offset);
$start_string_found = false;
$stop_string_found = false;
while (!
feof($this->handle))
{
$txt_block = fread($this->handle, $this->block_size);

if (!
$start_string_found) // 当未找到起始片段时
{
$strpos = mb_strpos($txt_block, $this->start_string);
if (
$strpos !== false)
{
// 剪掉开头的左侧块
$txt_block = mb_substr($txt_block, $strpos, $this->block_size);
$start_string_found = true;
}
}

if (
$start_string_found && !$stop_string_found) // 已找到起始片段,查找停止片段
{
$strpos = mb_strpos($txt_block, $this->stop_string);
if (
$strpos !== false)
{
$removed_block_size = mb_strlen($txt_block) - $strpos;
$txt_block = mb_substr($txt_block, 0, $strpos + mb_strlen($this->stop_string));
$stop_string_found = true;
$this->setFileOffset($removed_block_size);
}
}

if (
$stop_string_found) // 已找到停止片段,保持文件偏移,返回
{
$this->closeFile();
return
$txt_block;
}
}

$this->closeFile();
return
false;
}

/**
* 设置当前文件偏移量并考虑已删除的块大小
* 当前文件位置 = 当前文件偏移量 - 已删除的块大小
* @param int $removed_block_size
*/
private function setFileOffset($removed_block_size)
{
$this->file_offset = ftell($this->handle) - $removed_block_size;
}

/**
* 关闭当前文件
* @param void
* @return void
*/
private function openFile()
{
$this->handle = fopen($this->file, 'r');
}

/**
* 打开文件
* @param void
* @return void
*/
private function closeFile()
{
fclose($this->handle);
}
}
up
-2
lexica98 at gmail dot com
10 年前
不幸的是,即使使用 a+ 模式打开文件,fseek 也无法工作。如果你想创建一个文件然后能够转到文件中的任何位置,你必须在打开文件时使用追加模式,然后关闭它,然后再次以 r+ 模式打开它。
up
-1
necudeco at gmail dot com
14 年前
这是一个用于 Windows 系统的 tail php 脚本示例。

<?php
$n
= ( isset($_REQUEST['n']) == true )? $_REQUEST['n']:20;

$offset = -$n * 120;

$rs = fopen('C:/wamp/logs/apache_error.log','r');
if (
$rs === false )
die(
"无法打开日志文件");

fseek($rs,$offset,SEEK_END);

fgets($rs);
while(!
feof($rs))
{
$buffer = fgets($rs);
echo
$buffer;
echo
"<hr />";
}

fclose($rs);
?>
up
-3
lucky at somnius dot com dot ar
18 年前
如果文件不为空,或者更进一步说,如果它有多行,Jim(jim at lfchosting dot com)的最后一行问题的代码是完美的。但是,如果你使用的文件根本不包含换行符(即它是空的或只有一行),while 循环将无限期地卡住。

我知道这个脚本是为大文件设计的,这些文件总是至少包含几行,但使脚本具有防错功能会更聪明。

因此,这里对他的代码做了一个小修改。

<?php
function readLastLine ($file) {
$fp = @fopen($file, "r");

$pos = -1;
$t = " ";
while (
$t != "\n") {
if (!
fseek($fp, $pos, SEEK_END)) { // *** - fseek 成功返回 0,如果像在文件范围外查找字节那样没有成功,则返回 -1
$t = fgetc($fp);
$pos = $pos - 1;
} else {
// ***
rewind($fp); // ***
break; // ***
} // ***
}
$t = fgets($fp);
fclose($fp);
return
$t;
}
?>

添加和/或修改的行已用“// ***”标记。我希望这有帮助!

此致!
up
-2
steve at studio831 dot com
15 年前
修改了 @ben 的函数以处理大于 PHP_INT_MAX 字节的文件。

<?php
function longTail($file, $numLines = 100)
{
$fp = fopen($file, "r");
$chunk = 4096;
$fs = sprintf("%u", filesize($file));
$max = (intval($fs) == PHP_INT_MAX) ? PHP_INT_MAX : filesize($file);

for (
$len = 0; $len < $max; $len += $chunk) {
$seekSize = ($max - $len > $chunk) ? $chunk : $max - $len;

fseek($fp, ($len + $seekSize) * -1, SEEK_END);
$data = fread($fp, $seekSize) . $data;

if (
substr_count($data, "\n") >= $numLines + 1) {
preg_match("!(.*?\n){".($numLines)."}$!", $data, $match);
fclose($fp);
return
$match[0];
}
}
fclose($fp);
return
$data;
}
?>
up
-2
mhinks at gmail dot com
17 年前
这是一个我写的函数,用于在文件中二分查找一行文本,当文件太大而无法一次性读入内存,并且你希望比线性搜索更快时,这个函数特别有用。

function binary_search_in_file($filename, $search) {

// 打开文件
$fp = fopen($filename, 'r');

// 移动指针到文件末尾
fseek($fp, 0, SEEK_END);

// 获取最大值
$high = ftell($fp);

// 设置最小值
$low = 0;

while ($low <= $high) {
$mid = floor(($low + $high) / 2); // C 会向下取整

// 移动指针到中间位置
fseek($fp, $mid);

if($mid != 0){
// 读取一行以移动到行尾
$line = fgets($fp);
}

// 读取一行以获取数据
$line = fgets($fp);


if ($line == $search) {
fclose($fp);
返回 $line;
}
else {
if ($search < $line) {
$high = $mid - 1;
}
else {
$low = $mid + 1;
}
}
}

// 关闭指针
fclose($fp);

return FALSE;

}
up
-2
mail at ulf-kosack dot de
18 年前
这是对 ekow 代码的一个小扩展。
如果你想读取多行或多个文件。有时最后五行或十行很有用。

你只需要提交一个包含文件名的数组,以及可选的要读取的行数。

<?php
function read_logfiles($files, $lines=5)
{
foreach(
$files as $file_num => $file) {
if (
file_exists ($file) ) {
$handle = fopen($file, "r");
$linecounter = $lines;
$pos = -2;
$t = " ";
$text[$file_num] = "";
while (
$linecounter > 0) {
while (
$t != "\n") {
fseek($handle, $pos, SEEK_END);
$t = fgetc($handle);
$pos --;
}
$t = " ";
$text[$file_num] .= fgets($handle);
$linecounter --;
}
fclose ($handle);
} else {
$text[$file_num] = "文件不存在。";
}
}

return
$text;
?>
To Top