PHP Conference Japan 2024

fseek

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

fseek在檔案指標上搜尋

說明

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

設定由 stream 參照的檔案的檔案位置指示器。新的位置,以從檔案開頭算起的位元組數來衡量,是透過將 offset 加到 whence 指定的位置而獲得的。

一般來說,允許搜尋超過檔案結尾的位置;如果接著寫入資料,在檔案結尾和搜尋位置之間任何未寫入區域的讀取將會產生值為 0 的位元組。然而,某些資料流可能不支援此行為,尤其是在它們具有底層固定大小儲存空間時。

參數

stream

一個檔案系統指標 資源,通常使用 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 則註解

seeker at example com
16 年前
特別說明並引用如下:

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
3. 如果你使用 fseek() 將數據寫入文件,記得以 "r+" 模式打開文件
例如:

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

不要以 "a" 模式(附加模式)打開文件,因為它會將
文件指針放在文件末尾,並且不允許你
使用 fseek() 移動到文件中的較早位置(至少對我來說不行!)。此外,
也不要以 "w" 模式打開文件——雖然這會讓你位於
文件開頭——但它會清除文件中的所有數據。


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

花了我半天時間才搞懂 :/
匿名
13 年前
官方文檔指出並非所有串流都可搜尋。
你可以嘗試搜尋,並處理失敗的情況

<?php
if (fseek($stream, $offset, SEEK_CUR) === -1) {
// 處理錯誤
}
?>

或者,你可以使用 stream_get_meta_data 函數
https://php.dev.org.tw/stream_get_meta_data

<?php
function fseekable($stream) {
$meta = stream_get_meta_data($stream);
return
$meta['seekable'];
}
?>
lorenzo dot stanco at gmail dot com
11 年前
我想針對「從檔案讀取最後幾行」這個主題提供我的貢獻。我做了一些研究(真的是從這裡開始的),並針對不同的演算法和場景進行了許多測試,最後得到以下結果:

在 PHP 中讀取檔案最後幾行的最佳方法是什麼?
http://stackoverflow.com/a/15025877/995958

在那篇短文中,我嘗試分析所有不同的方法及其在不同檔案上的效能。

希望它有所幫助。
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;

} // while ($t != "\n") 的結束大括號

} // while ($linecounter > 0) 的結束大括號
}
fclose ($handle);
return array_reverse($text); // array_reverse 是可選的:你也可以只返回 $text 陣列,它由檔案的行組成。
}

現在的好處是,當你請求的行數超過檔案包含的行數時,你不會收到錯誤。在這種情況下,函數只會返回整個檔案內容。

synnus at gmail dot com
10 年前
使用 32 位元 (X86) 的 PHP 寫入 4GB 的虛擬檔案
如果您想寫入更大的檔案 (>4GB),請使用 64 位元 (X64) 的 PHP。
此檔案於 0.0041329860687256 秒內建立完成

CreatFileDummy('data_test.txt',4294967296);

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

Return true;
}

Synx
匿名
16 年前
致:seeker at example com
不過,請小心。
如果您以 (r+) 模式開啟檔案,您可以自由定位指標,但它會「覆寫」資料,而不是「附加」資料。

測試過這個

<?php
// file.txt 內容:
// "You can contribute your notes to the PHP manual from the comfort of your browser!"

$handler = fopen("file.txt", "r+");
fseek($handler, 0);
fwrite($handler, "want to add this");
?>
file.txt 的新內容會像這樣
"want to add thiste your notes to the PHP manual from the comfort of your browser!".

如果您真的想要在開頭附加內容,您必須先取得所有內容,儲存它,清除檔案,放入新的內容,然後將儲存的內容附加到結尾。
Lutz ( l_broedel at gmx dot net )
19 年前
基於 info at o08 dot com (感謝) 提供的以下函式,以下程式碼應該可以讓您從檔案中讀取單行,由行號識別 (從 1 開始)

<?
function 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);
return $line;
} //readLine()
?>
匿名
22 年前
不要對可能被平行處理程序或執行緒存取和更新的檔案使用 filesize() (因為 filesize() 的返回值會保存在快取中)。
如果需要執行 fread() 呼叫來讀取整個檔案,請改為鎖定已開啟的檔案,並使用 fseek($fp,0,SEEK_END) 和 ftell($fp) 來取得實際的檔案大小。
me at php dot net
11 年前
如何使用 fseek 讀取大型檔案 (超過 2GB+,最高可達任何大小,例如 4GB+、100GB+、100TB+、任何檔案大小、100PB,最大限制為 php_float_max)?

// 將檔案指標指向 50 GB
my_fseek($fp, floatval(50000000000), 1);

function my_fseek($fp, $pos, $first = 0) {

// 初始設定 pos 為 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);
}

}

希望這個有幫助。
Tom Pittlik
15 年前
以下的 tail 範例函式在嘗試開啟大型檔案時會傳回 PHP 記憶體限制錯誤。 由於 tail 很方便開啟大型日誌檔,因此這裡提供一個函式讓您可以(如果您有權限的話)

<?php

function unix_tail($lines,$file)
{
shell_exec("tail -n $lines $file > /tmp/phptail_$file");
$output = file_get_contents("/tmp/phptail_$file");
unlink("/tmp/phptail_$file");
return
$output;
}

?>
kavoshgar3 at gmail dot com
13 年前
有時我們想要從檔案的最後一行讀取到檔案的開頭。我使用以下方法。
<?php
function 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]);
return
implode('',$read);
}
return
strrev(rtrim($read,"\n\r"));
}
//如果 $revers=false 函式會回傳->
//第 1000 行: 我是第 1000 行
//第 1001 行: 我是第 1001 行
//第 1002 行: 我是最後一行
//但如果 $revers=true 函式會回傳->
//第 1002 行: 我是最後一行
//第 1001 行: 我是第 1001 行
//第 1000 行: 我是第 1000 行
?>
試試看吧!如果可以運作請寄信給我! ;-)
marin at sagovac dot com
8 年前
為了提升效能,搜尋到特定程式碼行數後,就從 while 迴圈中跳出。使用 SEEK_SET 搜尋到特定行數並取得該行內容。如果 $range 為 '0',則顯示搜尋到的那一行。如果設為 '2',則顯示目前行加上上下各兩行。

這對於在非常大的檔案中取得特定範圍的行數內容非常有用。為了提升效能,使用搜尋的方式比在 while 迴圈中迭代更有效率。

我建立了一個函式,它會讀取檔案、計算行數,並將每一行的位元組位置儲存到陣列中。如果達到 `linenum` 指定的最大值,它會從 while 迴圈中跳出以維持效能,然後在新的迴圈函式中搜尋位元組位置以取得檔案內容。

function 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++) {

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

}
}

return $buffer;
}

測試結果 (1.3 GB 檔案,100,000,000 行程式碼,搜尋到第 300,000 行)

string(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
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;
}
?>
ben at nullcreations dot net
16 年前
更簡便的 PHP tail() 函式

<?php
函數 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;
}
?>
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);
ekow[at]te.ugm.ac.id
18 年前
對 chenganeyou at eyou dot com 的程式碼進行一些修正,以讀取最後一行。
$linenumber = sizeof($file)-1;
應該改為
$linenumber = sizeof($contents)-1;
因為 sizeof 會計算陣列元素,而不是檔案大小。
<?php
函式 readlastline($file)
{
$linecontent = " ";
$contents = file($file);
$linenumber = sizeof($contents)-1;
$linecontet = $contents[$linenumber];
unset(
$contents,$linenumber);
return
$linecontent;
}
?>
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-- ;
}
}
jim at lfchosting dot com
21 年前
以下是一個返回檔案最後一行的函式。這應該比讀取整個檔案直到最後一行要快。如果您想加快速度,可以將 $pos 設定為略大於行長度的數字。我處理的檔案長度各不相同,所以這對我來說很有效。

<?php
函式 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;
}
?>
匿名
13 年前
我需要串流一個文字檔(這裡是一個大型的 XML 檔案)來分塊取得節點。我找不到更簡短的方法。所以我寫了這個類別。

功能:串流整個檔案並返回兩個搜尋字串之間的內容,包含搜尋字串本身(多位元組安全)

希望它可以幫助任何人。

備註:它缺乏任何針對不存在的檔案/讀取錯誤的布林檢查/例外處理。

<?php
/**
* Reads txt-files blockwise
* Usage:
$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())
{
// use $txt_block for something
}
*/

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

/**
* Sett class arguments
* @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'];
}

/**
* Get next textblock within a file
* @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) // while not start start snippet found
{
$strpos = mb_strpos($txt_block, $this->start_string);
if (
$strpos !== false)
{
// cut of first left chunk
$txt_block = mb_substr($txt_block, $strpos, $this->block_size);
$start_string_found = true;
}
}

if (
$start_string_found && !$stop_string_found) // start snipped found, looking for stop snippet
{
$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) // stop-snippet found, keep file offset, return
{
$this->closeFile();
return
$txt_block;
}
}

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

/**
* Set current file offset and consider the removed block size
* current file position = current file offset - removed block size
* @param int $removed_block_size
*/
private function setFileOffset($removed_block_size)
{
$this->file_offset = ftell($this->handle) - $removed_block_size;
}

/**
* close current file
* @param void
* @return void
*/
private function openFile()
{
$this->handle = fopen($this->file, 'r');
}

/**
* open file
* @param void
* @return void
*/
private function closeFile()
{
fclose($this->handle);
}
}
lexica98 at gmail dot com
10 年前
很遺憾,以 a+ 模式開啟檔案也無法搭配 fseek 使用。如果您希望建立一個檔案,然後能夠移動到檔案中的任何位置,您必須先以附加模式開啟檔案,然後關閉它,再以 r+ 模式重新開啟。
necudeco at gmail dot com
14 年前
這是一個適用於 Windows 系統的 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);
?>
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;
}
?>

標記了新增及/或修改的行數 "// ***"。希望這個說明對您有所幫助!

此致!
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;
}
?>
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 語言的 floor() 函式會幫你處理

//移至檔案中間位置
fseek($fp, $mid);

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

//讀取一行以取得資料
$line = fgets($fp);


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

//關閉檔案指標
fclose($fp);

return FALSE;

}
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