讓我們算一算有多少種方法
處理諸如 PHP 之類的現代程式語言的樂趣之一就是有大量的選項可用。PHP 可以輕鬆地贏得 Perl 的座右銘“There's more than one way to do it”(並非只有一種方法可做這件事),尤其是在檔案處理上。但是在這麼多可用的選項中,哪一種是完成作業的最佳工具?當然,實際答案取決於解析檔案的目標,因此值得花時間探究所有選項。
fopen
方法可能是以前的 C 和 C++ 程式設計師最熟悉的,因為如果您使用過這些語言,那麼它們或多或少都是您已掌握多年的工具。對於這些方法中的任何一種,通過使用 fopen
(用於讀取資料的函式)的標準方法開啟檔案,然後使用 fclose
關閉檔案,如清單 1 所示。
$file_handle = fopen("myfile", "r"); while (!feof($file_handle)) { $line = fgets($file_handle); echo $line; } fclose($file_handle); |
雖然大多數具有多年程式設計經驗的程式設計師都熟悉這些函式,但是讓我對這些函式進行分解。有效地執行以下步驟:
- 開啟檔案。
$file_handle
儲存了一個對檔案本身的引用。 - 檢查您是否已到達檔案的末尾。
- 繼續讀取檔案,直至到達檔案末尾,邊讀取邊列印每行。
- 關閉檔案。
記住這些步驟,我將回顧在這裡使用的每個檔案函式。
fopen
函式將建立與檔案的連線。我之所以說“建立連線”,是因為除了開啟檔案之外,fopen
還可以開啟一個 URL:
$fh = fopen("http://127.0.0.1/", "r"); |
這行程式碼將建立一個與以上頁面的連線,並允許您開始像讀取一個本地檔案一樣讀取它。
注: fopen
中使用的 "r"
將指示檔案以只讀方式開啟。由於將資料寫入檔案不在本文的討論範圍內,因此我將不列出所有其他選項。但是,如果是從二進位制檔案讀取以獲得跨平臺相容性,則應當將 "r"
更改為 "rb"
。稍後您將看到這樣的示例。
feof
命令將檢測您是否已經讀到檔案的末尾並返回 True 或 False。清單 1 中的迴圈將繼續執行,直至您達到檔案“myfile”的末尾。注:如果讀取的是 URL 並且套接字由於不再有任何資料可以讀取而超時,則 feof
也將返回 False。
向前跳至清單 1 的末尾,fclose
將實現與 fopen
相反的功能:它將關閉指向檔案或 URL 的連線。執行此函式後,您將不再能夠從檔案或套接字中讀取任何資訊。
在清單 1 中回跳幾行,您就到達了檔案處理的核心:實際讀取檔案。fgets
函式是處理第一個示例的首選武器。它將從檔案中提取一行資料並將其作為字串返回。在那之後,您可以列印或者以別的方式處理資料。清單 1 中的示例將精細地列印整個檔案。
如果決定限制處理資料塊的大小,您可以將一個引數新增到 fgets
中限制最大行長度。例如,使用以下程式碼將行長度限制為 80 個字元:
$string = fgets($file_handle, 81); |
回想 C 中的“/0”字串末尾終止符,將長度設為比實際所需值大一的數字。因而,如果需要 80 個字元,則以上示例使用 81。應養成以下習慣:只要對此函式使用行限制,就新增該額外字元。
fgets
函式是多個檔案讀取函式中惟一一個可用的。它是一個更常用的函式,因為逐行解析通常會有意義。事實上,幾個其他函式也可以提供類似功能。但是,您並非總是需要逐行解析。
這時就需要使用 fread
。fread
函式與 fgets
的處理目標略有不同:它趨於從二進位制檔案(即,並非主要包含人類可閱讀的文字的檔案)中讀取資訊。由於“行”的概念與二進位制檔案無關(邏輯資料結構通常都不是由新行終止),因此您必須指定需要讀入的位元組數。
$fh = fopen("myfile", "rb"); $data = fread($file_handle, 4096); |
以上程式碼將讀取 4,096 位元組 (4 KB) 的資料。注:不管指定多少位元組,fread
都不會讀取超過 8,192 個位元組 (8 KB)。
假定檔案大小不超過 8 KB,則以下程式碼應當能將整個檔案讀入一個字串。
$fh = fopen("myfile", "rb"); $data = fread($fh, filesize("myfile")); fclose($fh); |
如果檔案長度大於此值,則只能使用迴圈將其餘內容讀入。
回到字串處理,fscanf
同樣遵循傳統的 C 檔案庫函式。如果您不熟悉它,則 fscanf
將把欄位資料從檔案讀入變數中。
list ($field1, $field2, $field3) = fscanf($fh, "%s %s %s"); |
此函式使用的格式字串在很多地方都有描述(如 PHP.net 中),故在此不再贅述。可以這樣說,字串格式化極為靈活。值得注意的是所有欄位都放在函式的返回值中。(在 C 中,它們都被作為引數傳遞。)
fgetss
函式不同於傳統檔案函式並使您能更好地瞭解 PHP 的力量。該函式的功能類似於 fgets
函式,但將去掉發現的任何 HTML 或 PHP 標記,只留下純文字。檢視如下所示的 HTML 檔案。
<html> <head><title>My title</title></head> <body> <p>If you understand what "Cause there ain't no one for to give you no pain" means then you listen to too much of the band America</p> </body> </html> |
然後通過 fgetss
函式過濾它。
$file_handle = fopen("myfile", "r"); while (!feof($file_handle)) { echo = fgetss($file_handle); } fclose($file_handle); |
以下是輸出:
My title If you understand what "Cause there ain't no one for to give you no pain" means then you listen to too much of the band America |
無論怎樣讀取檔案,您都可以使用 fpassthru
將其餘資料轉儲到標準輸出通道。
fpassthru($fh); |
此外,此函式將列印資料,因此無需使用變數獲取資料。
當然,以上函式只允許順序讀取檔案。更復雜的檔案可能要求您來回跳轉到檔案的不同部分。這時就用得著 fseek
了。
fseek($fh, 0); |
以上示例將跳轉回檔案的開頭。如果不需要完全返回 —— 我們可設定返回千位元組 —— 然後就可以這樣寫:
fseek($fh, 1024); |
從 PHP V4.0 開始,您有一些其他選項。例如,如果需要從當前位置向前跳轉 100 個位元組,則可以嘗試使用:
fseek($fh, 100, SEEK_CUR); |
類似地,可以使用以下程式碼向後跳轉 100 個位元組:
fseek($fh, -100, SEEK_CUR); |
如果需要向後跳轉至檔案末尾前 100 個位元組處,則應使用 SEEK_END
。
fseek($fh, -100, SEEK_END); |
在到達新位置後,可以使用 fgets
、fscanf
或任何其他方法讀取資料。
注:不能將 fseek
用於引用 URL 的檔案處理。
現在,我們將接觸到一些 PHP 的更獨特的檔案處理功能:用一兩行處理大塊資料。例如,如何提取檔案並在 Web 頁面上顯示其全部內容?好的,您看到了 fgets
使用迴圈的示例。但是如何能夠使此過程變得更簡單?用 fgetcontents
會使過程超級簡單,該方法將把整個檔案放入一個字串中。
$my_file = file_get_contents("myfilename"); echo $my_file; |
雖然它不是最好的做法,但是可以將此命令更簡明地寫為:
echo file_get_contents("myfilename"); |
本文主要介紹的是如何處理本地檔案,但是值得注意的是您還可以用這些函式提取、回顯和解析其他 Web 頁面。
echo file_get_contents("http://127.0.0.1/"); |
此命令等效於:
$fh = fopen("http://127.0.0.1/", "r"); fpassthru($fh); |
您一定會檢視此命令並認為:“那還是太費力”。PHP 開發人員同意您的看法。因此可以將以上命令縮短為:
readfile("http://127.0.0.1/"); |
readfile
函式將把檔案或 Web 頁面的全部內容轉儲到預設的輸出緩衝區。預設情況下,如果失敗,此命令將列印錯誤訊息。要避免此行為(如果需要),請嘗試:
@readfile("http://127.0.0.1/"); |
當然,如果確實需要解析檔案,則 file_get_contents
返回的單個字串可能有些讓人吃不消。您的第一反應可能是用 split()
函式將它分解一下。
$array = split("/n", file_get_contents("myfile")); |
但是既然已經有一個很好的函式為您執行此操作為什麼還要這樣大費周章?PHP 的 file()
函式一步即可完成此操作:它將返回分為若干行的字串陣列。
$array = file("myfile"); |
應當注意的是,以上兩個示例有一點細微差別。雖然 split
命令將刪除新行,但是當使用 file
命令(與 fgets
命令一樣)時,新行仍將被附加到陣列中的字串上。
但是,PHP 的力量還遠不止於此。您可以在一條命令中使用 parse_ini_file
解析整個 PHP 樣式的 .ini 檔案。parse_ini_file
命令接受類似清單 4 所示的檔案。
; Comment [personal information] name = "King Arthur" quest = To seek the holy grail favorite color = Blue [more stuff] Samuel Clemens = Mark Twain Caryn Johnson = Whoopi Goldberg |
以下命令將把此檔案轉儲為陣列,然後列印該陣列:
$file_array = parse_ini_file("holy_grail.ini"); print_r $file_array; |
以下輸出的是結果:
Array ( [name] => King Arthur [quest] => To seek the Holy Grail [favorite color] => Blue [Samuel Clemens] => Mark Twain [Caryn Johnson] => Whoopi Goldberg ) |
當然,您可能注意到此命令合併了各個部分。這是預設行為,但是您可以通過將第二個引數傳遞給 parse_ini_file
輕鬆地修正它:process_sections
,這是一個布林型變數。將 process_sections
設為 True。
$file_array = parse_ini_file("holy_grail.ini", true); print_r $file_array; |
並且您將獲得以下輸出:
Array ( [personal information] => Array ( [name] => King Arthur [quest] => To seek the Holy Grail [favorite color] => Blue ) [more stuff] => Array ( [Samuel Clemens] => Mark Twain [Caryn Johnson] => Whoopi Goldberg ) ) |
PHP 將把資料放入可以輕鬆解析的多維陣列中。
對於 PHP 檔案處理來說,這只是冰山一角。諸如 tidy_parse_file
和 xml_parse
之類的更復雜的函式可以分別幫助您處理 HTML 和 XML 文件。有關這些特殊函式的使用細節,請參閱 參考資料。如果您要處理那些型別的檔案,則那些參考資料值得一看,但不必過度考慮本文中談到的每種可能遇到的檔案型別,下面是一些用於處理到目前為止介紹的函式的很好的通用規則。
絕不要假定程式中的一切都將按計劃執行。例如,如果您要查詢的檔案已被移動該當如何?如果許可權已被改變而無法讀取其內容又當如何?您可以通過使用 file_exists
和 is_readable
預先檢查這些問題。
清單 7. 使用 file_exists 和 is_readable
$filename = "myfile"; if (file_exists($filename) && is_readable ($filename)) { $fh = fopen($filename, "r"); # Processing fclose($fh); } |
但是,在實踐中,用這樣的程式碼可能太繁瑣了。處理 fopen
的返回值更簡單並且更準確。
if ($fh = fopen($filename, "r")) { # Processing fclose($fh); } |
由於失敗時 fopen
將返回 False,這將確保僅當檔案成功開啟後才執行檔案處理。當然,如果檔案不存在或者不可讀,您可以期望一個負返回值。這將使這個檢查可以檢查所有可能遇到的問題。此外,如果開啟失敗,可以退出程式或讓程式顯示錯誤訊息。
如 fopen
函式一樣,file_get_contents
、file
和 readfile
函式都在開啟失敗或處理檔案失敗時返回 False。fgets
、fgetss
、fread
、fscanf
和 fclose
函式在出錯時也返回 False。當然,除 fclose
以外,您可能已經對這些函式的返回值都進行了處理。使用 fclose
時,即使檔案處理未正常關閉,也不會執行什麼操作,因此通常不必檢查 fclose
的返回值。
PHP 不缺讀取和解析檔案的有效方法。諸如 fread
之類的典型函式可能在大多數時候都是最佳的選擇,或者當 readfile
剛好能滿足任務需要時,您可能會發現自己更為 readfile
的簡單所吸引。它實際上取決於所要完成的操作。
如果要處理大量資料,fscanf
將能證明自己的價值並比使用 file
附帶 split
和 sprintf
命令更有效率。相反,如果要回顯只做了少許修改的大量文字,則使用 file
、file_get_contents
或 readfile
可能更合適。使用 PHP 進行快取或者建立權宜的代理伺服器時可能就屬於這種情況。
PHP 給您提供了大量處理檔案的工具。深入瞭解這些工具並瞭解哪些工具最適合於要處理的專案。您已擁有很多的選擇,因此好好地利用它們享受使用 PHP 處理檔案的樂趣。
學習
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文 。
- PHP.net 是所有 PHP 的完全命令參考。
- 查閱“PHP 推薦讀物列表”。
- 查閱 IBM developerWorks 的 PHP 專案資源中心 擴充套件 PHP 技巧。
- 閱讀“PHP by example, Part 1”發現 PHP 用於構建複雜且功能強大的 Web 相關程式的簡單方法。
- 瞭解本文未涉及到的
xml_parse
函式的資訊。 - 瞭解本文未涉及到的
tidy_parse_file
函式的資訊。 - PHP.net 是 PHP 開發人員的資源。
- 瀏覽 developerWorks 上的所有 PHP 內容。
- 收聽針對軟體開發人員的有趣訪談和討論,一定要訪問 developerWorks 的 podcast。
- 隨時關注 developerWorks 的 技術事件和網路廣播。
- 查閱最近將在全球舉辦的面向 IBM 開放原始碼開發人員的研討會、交易展覽、網路廣播和其他 活動。
- 訪問 developerWorks 開放原始碼專區,獲得豐富的 how-to 資訊、工具和專案更新,幫助您用開放原始碼技術進行開發,並與 IBM 產品結合使用。
- 訪問 Safari 線上書店 瀏覽開放原始碼技術的各種參考資料。
獲得產品和技術
- 使用 IBM 試用軟體 構建您的下一個開發專案,這些軟體可以通過下載或從 DVD 中獲得。
討論
- 參與 developerWorks blog,加入 developerWorks 社群。
- 參與 developerWorks PHP 開發者論壇。