級(jí)別: 中級(jí) Roger McCoy (rogermccoy@gmail.com), IT Specialist, 自由撰稿人
2007 年 3 月 06 日 了解如何使用 PHP 的各種文件函數(shù)。查看諸如 fopen 、fclose 和 feof 之類的基本文件函數(shù);了解諸如 fgets 、fgetss 和 fscanf 之類的讀取函數(shù)。并且發(fā)現(xiàn)用一兩行代碼處理整個(gè)文件的函數(shù)。 讓我們算一算有多少種方法 處 理諸如 PHP 之類的現(xiàn)代編程語(yǔ)言的樂趣之一就是有大量的選項(xiàng)可用。PHP 可以輕松地贏得 Perl 的座右銘“There‘s more than one way to do it”(并非只有一種方法可做這件事),尤其是在文件處理上。但是在這么多可用的選項(xiàng)中,哪一種是完成作業(yè)的最佳工具?當(dāng)然,實(shí)際答案取決于解析文件的目 標(biāo),因此值得花時(shí)間探究所有選項(xiàng)。
傳統(tǒng)的 fopen 方法 fopen 方法可能是以前的 C 和 C++ 程序員最熟悉的,因?yàn)槿绻褂眠^這些語(yǔ)言,那么它們或多或少都是您已掌握多年的工具。對(duì)于這些方法中的任何一種,通過使用 fopen (用于讀取數(shù)據(jù)的函數(shù))的標(biāo)準(zhǔn)方法打開文件,然后使用 fclose 關(guān)閉文件,如清單 1 所示。 清單 1. 用 fgets 打開并讀取文件 $file_handle = fopen("myfile", "r"); while (!feof($file_handle)) { $line = fgets($file_handle); echo $line; } fclose($file_handle);
| 雖然大多數(shù)具有多年編程經(jīng)驗(yàn)的程序員都熟悉這些函數(shù),但是讓我對(duì)這些函數(shù)進(jìn)行分解。有效地執(zhí)行以下步驟: - 打開文件。
$file_handle 存儲(chǔ)了一個(gè)對(duì)文件本身的引用。 - 檢查您是否已到達(dá)文件的末尾。
- 繼續(xù)讀取文件,直至到達(dá)文件末尾,邊讀取邊打印每行。
- 關(guān)閉文件。
記住這些步驟,我將回顧在這里使用的每個(gè)文件函數(shù)。 fopen fopen 函數(shù)將創(chuàng)建與文件的連接。我之所以說“創(chuàng)建連接”,是因?yàn)槌舜蜷_文件之外,fopen 還可以打開一個(gè) URL: $fh = fopen("http://127.0.0.1/", "r");
| 這行代碼將創(chuàng)建一個(gè)與以上頁(yè)面的連接,并允許您開始像讀取一個(gè)本地文件一樣讀取它。 注: fopen 中使用的 "r" 將指示文件以只讀方式打開。由于將數(shù)據(jù)寫入文件不在本文的討論范圍內(nèi),因此我將不列出所有其他選項(xiàng)。但是,如果是從二進(jìn)制文件讀取以獲得跨平臺(tái)兼容性,則應(yīng)當(dāng)將 "r" 更改為 "rb" 。稍后您將看到這樣的示例。 feof feof 命令將檢測(cè)您是否已經(jīng)讀到文件的末尾并返回 True 或 False。清單 1 中的循環(huán)將繼續(xù)執(zhí)行,直至您達(dá)到文件“myfile”的末尾。注:如果讀取的是 URL 并且套接字由于不再有任何數(shù)據(jù)可以讀取而超時(shí),則 feof 也將返回 False。 fclose 向前跳至清單 1 的末尾,fclose 將實(shí)現(xiàn)與 fopen 相反的功能:它將關(guān)閉指向文件或 URL 的連接。執(zhí)行此函數(shù)后,您將不再能夠從文件或套接字中讀取任何信息。 fgets 在清單 1 中回跳幾行,您就到達(dá)了文件處理的核心:實(shí)際讀取文件。fgets 函數(shù)是處理第一個(gè)示例的首選武器。它將從文件中提取一行數(shù)據(jù)并將其作為字符串返回。在那之后,您可以打印或者以別的方式處理數(shù)據(jù)。清單 1 中的示例將精細(xì)地打印整個(gè)文件。 如果決定限制處理數(shù)據(jù)塊的大小,您可以將一個(gè)參數(shù)添加到 fgets 中限制最大行長(zhǎng)度。例如,使用以下代碼將行長(zhǎng)度限制為 80 個(gè)字符: $string = fgets($file_handle, 81);
| 回想 C 中的“\0”字符串末尾終止符,將長(zhǎng)度設(shè)為比實(shí)際所需值大一的數(shù)字。因而,如果需要 80 個(gè)字符,則以上示例使用 81。應(yīng)養(yǎng)成以下習(xí)慣:只要對(duì)此函數(shù)使用行限制,就添加該額外字符。 fread fgets 函數(shù)是多個(gè)文件讀取函數(shù)中惟一一個(gè)可用的。它是一個(gè)更常用的函數(shù),因?yàn)橹鹦薪馕鐾ǔ?huì)有意義。事實(shí)上,幾個(gè)其他函數(shù)也可以提供類似功能。但是,您并非總是需要逐行解析。 這時(shí)就需要使用 fread 。fread 函數(shù)與 fgets 的處理目標(biāo)略有不同:它趨于從二進(jìn)制文件(即,并非主要包含人類可閱讀的文本的文件)中讀取信息。由于“行”的概念與二進(jìn)制文件無(wú)關(guān)(邏輯數(shù)據(jù)結(jié)構(gòu)通常都不是由新行終止),因此您必須指定需要讀入的字節(jié)數(shù)。 $fh = fopen("myfile", "rb"); $data = fread($file_handle, 4096);
| | 使用二進(jìn)制數(shù)據(jù) 注意:此函數(shù)的示例已經(jīng)使用了略微不同于 fopen 的參數(shù)。當(dāng)處理二進(jìn)制數(shù)據(jù)時(shí),始終要記得將 b 選項(xiàng)包含在 fopen 中。如果跳過這一點(diǎn),Microsoft? Windows? 系統(tǒng)可能無(wú)法正確處理文件,因?yàn)樗鼈儗⒁圆煌姆绞教幚硇滦小H绻幚淼氖?Linux? 系統(tǒng)(或其他某個(gè) UNIX? 變種),則這可能看似沒什么關(guān)系。但即使不是針對(duì) Windows 開發(fā)的,這樣做也將獲得良好的跨平臺(tái)可維護(hù)性,并且也是應(yīng)當(dāng)遵循的一個(gè)好習(xí)慣。 | | 以上代碼將讀取 4,096 字節(jié) (4 KB) 的數(shù)據(jù)。注:不管指定多少字節(jié),fread 都不會(huì)讀取超過 8,192 個(gè)字節(jié) (8 KB)。 假定文件大小不超過 8 KB,則以下代碼應(yīng)當(dāng)能將整個(gè)文件讀入一個(gè)字符串。 $fh = fopen("myfile", "rb"); $data = fread($fh, filesize("myfile")); fclose($fh);
| 如果文件長(zhǎng)度大于此值,則只能使用循環(huán)將其余內(nèi)容讀入。 fscanf 回到字符串處理,fscanf 同樣遵循傳統(tǒng)的 C 文件庫(kù)函數(shù)。如果您不熟悉它,則 fscanf 將把字段數(shù)據(jù)從文件讀入變量中。 list ($field1, $field2, $field3) = fscanf($fh, "%s %s %s");
| 此函數(shù)使用的格式字符串在很多地方都有描述(如 PHP.net 中),故在此不再贅述??梢赃@樣說,字符串格式化極為靈活。值得注意的是所有字段都放在函數(shù)的返回值中。(在 C 中,它們都被作為參數(shù)傳遞。) fgetss fgetss 函數(shù)不同于傳統(tǒng)文件函數(shù)并使您能更好地了解 PHP 的力量。該函數(shù)的功能類似于 fgets 函數(shù),但將去掉發(fā)現(xiàn)的任何 HTML 或 PHP 標(biāo)記,只留下純文本。查看如下所示的 HTML 文件。 清單 2. 樣例 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 函數(shù)過濾它。 清單 3. 使用 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 函數(shù) 無(wú)論怎樣讀取文件,您都可以使用 fpassthru 將其余數(shù)據(jù)轉(zhuǎn)儲(chǔ)到標(biāo)準(zhǔn)輸出通道。 此外,此函數(shù)將打印數(shù)據(jù),因此無(wú)需使用變量獲取數(shù)據(jù)。 非線性文件處理:跳躍訪問 當(dāng)然,以上函數(shù)只允許順序讀取文件。更復(fù)雜的文件可能要求您來回跳轉(zhuǎn)到文件的不同部分。這時(shí)就用得著 fseek 了。 以上示例將跳轉(zhuǎn)回文件的開頭。如果不需要完全返回 —— 我們可設(shè)定返回千字節(jié) —— 然后就可以這樣寫: 從 PHP V4.0 開始,您有一些其他選項(xiàng)。例如,如果需要從當(dāng)前位置向前跳轉(zhuǎn) 100 個(gè)字節(jié),則可以嘗試使用: fseek($fh, 100, SEEK_CUR);
| 類似地,可以使用以下代碼向后跳轉(zhuǎn) 100 個(gè)字節(jié): fseek($fh, -100, SEEK_CUR);
| 如果需要向后跳轉(zhuǎn)至文件末尾前 100 個(gè)字節(jié)處,則應(yīng)使用 SEEK_END 。 fseek($fh, -100, SEEK_END);
| 在到達(dá)新位置后,可以使用 fgets 、fscanf 或任何其他方法讀取數(shù)據(jù)。 注:不能將 fseek 用于引用 URL 的文件處理。
提取整個(gè)文件 現(xiàn)在,我們將接觸到一些 PHP 的更獨(dú)特的文件處理功能:用一兩行處理大塊數(shù)據(jù)。例如,如何提取文件并在 Web 頁(yè)面上顯示其全部?jī)?nèi)容?好的,您看到了 fgets 使用循環(huán)的示例。但是如何能夠使此過程變得更簡(jiǎn)單?用 fgetcontents 會(huì)使過程超級(jí)簡(jiǎn)單,該方法將把整個(gè)文件放入一個(gè)字符串中。 $my_file = file_get_contents("myfilename"); echo $my_file;
| 雖然它不是最好的做法,但是可以將此命令更簡(jiǎn)明地寫為: echo file_get_contents("myfilename");
| 本文主要介紹的是如何處理本地文件,但是值得注意的是您還可以用這些函數(shù)提取、回顯和解析其他 Web 頁(yè)面。 echo file_get_contents("http://127.0.0.1/");
| 此命令等效于: $fh = fopen("http://127.0.0.1/", "r"); fpassthru($fh);
| 您一定會(huì)查看此命令并認(rèn)為:“那還是太費(fèi)力”。PHP 開發(fā)人員同意您的看法。因此可以將以上命令縮短為: readfile("http://127.0.0.1/");
| readfile 函數(shù)將把文件或 Web 頁(yè)面的全部?jī)?nèi)容轉(zhuǎn)儲(chǔ)到默認(rèn)的輸出緩沖區(qū)。默認(rèn)情況下,如果失敗,此命令將打印錯(cuò)誤消息。要避免此行為(如果需要),請(qǐng)嘗試: @readfile("http://127.0.0.1/");
| 當(dāng)然,如果確實(shí)需要解析文件,則 file_get_contents 返回的單個(gè)字符串可能有些讓人吃不消。您的第一反應(yīng)可能是用 split() 函數(shù)將它分解一下。 $array = split("\n", file_get_contents("myfile"));
| 但是既然已經(jīng)有一個(gè)很好的函數(shù)為您執(zhí)行此操作為什么還要這樣大費(fèi)周章?PHP 的 file() 函數(shù)一步即可完成此操作:它將返回分為若干行的字符串?dāng)?shù)組。 應(yīng)當(dāng)注意的是,以上兩個(gè)示例有一點(diǎn)細(xì)微差別。雖然 split 命令將刪除新行,但是當(dāng)使用 file 命令(與 fgets 命令一樣)時(shí),新行仍將被附加到數(shù)組中的字符串上。 但是,PHP 的力量還遠(yuǎn)不止于此。您可以在一條命令中使用 parse_ini_file 解析整個(gè) PHP 樣式的 .ini 文件。parse_ini_file 命令接受類似清單 4 所示的文件。 清單 4. 樣例 .ini 文件 ; 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
| 以下命令將把此文件轉(zhuǎn)儲(chǔ)為數(shù)組,然后打印該數(shù)組: $file_array = parse_ini_file("holy_grail.ini"); print_r $file_array;
| 以下輸出的是結(jié)果: Listing 5. 輸出 Array ( [name] => King Arthur [quest] => To seek the Holy Grail [favorite color] => Blue [Samuel Clemens] => Mark Twain [Caryn Johnson] => Whoopi Goldberg )
| 當(dāng)然,您可能注意到此命令合并了各個(gè)部分。這是默認(rèn)行為,但是您可以通過將第二個(gè)參數(shù)傳遞給 parse_ini_file 輕松地修正它:process_sections ,這是一個(gè)布爾型變量。將 process_sections 設(shè)為 True。 $file_array = parse_ini_file("holy_grail.ini", true); print_r $file_array;
| 并且您將獲得以下輸出: 清單 6. 輸出 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 將把數(shù)據(jù)放入可以輕松解析的多維數(shù)組中。 對(duì)于 PHP 文件處理來說,這只是冰山一角。諸如 tidy_parse_file 和 xml_parse 之類的更復(fù)雜的函數(shù)可以分別幫助您處理 HTML 和 XML 文檔。有關(guān)這些特殊函數(shù)的使用細(xì)節(jié),請(qǐng)參閱 參考資料。如果您要處理那些類型的文件,則那些參考資料值得一看,但不必過度考慮本文中談到的每種可能遇到的文件類型,下面是一些用于處理到目前為止介紹的函數(shù)的很好的通用規(guī)則。
最佳實(shí)踐 絕不要假定程序中的一切都將按計(jì)劃運(yùn)行。例如,如果您要查找的文件已被移動(dòng)該當(dāng)如何?如果權(quán)限已被改變而無(wú)法讀取其內(nèi)容又當(dāng)如何?您可以通過使用 file_exists 和 is_readable 預(yù)先檢查這些問題。 清單 7. 使用 file_exists 和 is_readable $filename = "myfile"; if (file_exists($filename) && is_readable ($filename)) { $fh = fopen($filename, "r"); # Processing fclose($fh); }
| 但是,在實(shí)踐中,用這樣的代碼可能太繁瑣了。處理 fopen 的返回值更簡(jiǎn)單并且更準(zhǔn)確。 if ($fh = fopen($filename, "r")) { # Processing fclose($fh); }
| 由于失敗時(shí) fopen 將返回 False,這將確保僅當(dāng)文件成功打開后才執(zhí)行文件處理。當(dāng)然,如果文件不存在或者不可讀,您可以期望一個(gè)負(fù)返回值。這將使這個(gè)檢查可以檢查所有可能遇到的問題。此外,如果打開失敗,可以退出程序或讓程序顯示錯(cuò)誤消息。 如 fopen 函數(shù)一樣,file_get_contents 、file 和 readfile 函數(shù)都在打開失敗或處理文件失敗時(shí)返回 False。fgets 、fgetss 、fread 、fscanf 和 fclose 函數(shù)在出錯(cuò)時(shí)也返回 False。當(dāng)然,除 fclose 以外,您可能已經(jīng)對(duì)這些函數(shù)的返回值都進(jìn)行了處理。使用 fclose 時(shí),即使文件處理未正常關(guān)閉,也不會(huì)執(zhí)行什么操作,因此通常不必檢查 fclose 的返回值。
由您來選擇 PHP 不缺讀取和解析文件的有效方法。諸如 fread 之類的典型函數(shù)可能在大多數(shù)時(shí)候都是最佳的選擇,或者當(dāng) readfile 剛好能滿足任務(wù)需要時(shí),您可能會(huì)發(fā)現(xiàn)自己更為 readfile 的簡(jiǎn)單所吸引。它實(shí)際上取決于所要完成的操作。 如果要處理大量數(shù)據(jù),fscanf 將能證明自己的價(jià)值并比使用 file 附帶 split 和 sprintf 命令更有效率。相反,如果要回顯只做了少許修改的大量文本,則使用 file 、file_get_contents 或 readfile 可能更合適。使用 PHP 進(jìn)行緩存或者創(chuàng)建權(quán)宜的代理服務(wù)器時(shí)可能就屬于這種情況。 PHP 給您提供了大量處理文件的工具。深入了解這些工具并了解哪些工具最適合于要處理的項(xiàng)目。您已擁有很多的選擇,因此好好地利用它們享受使用 PHP 處理文件的樂趣。 |