總算來到我們最關心的部分了,也就是 f 相關函數的操作。基本上大部分的文件操作都是以今天學習的這些內容爲基礎的,話不多說,我們就一個一個的來學習學習吧。
文件讀取
文件的讀取其實非常簡單,fopen() 打開句柄,fread() 讀取內容,fclose() 關閉句柄,一套流程下來操作就完成了。
$f = fopen('./test.txt', 'r+');
while (!feof($f)) {
$contents = fread($f, 4);
echo $contents, PHP_EOL;
}
// Rain
// is
// fall
// ing
// all
// arou
// nd,
// It f
// alls
// on
// ……
// ……
fopen() 函數的第二個參數是我們可以操作的權限。這個大家應該不會陌生,w 就是可寫,r 就是可讀,r+ 就是讀寫方式打開並將文件指針指向文件頭,a 是追加寫入。
模式 | 說明 | |
---|---|---|
'r' | 只讀方式打開,將文件指針指向文件頭。 | |
'r+' | 讀寫方式打開,將文件指針指向文件頭。 | |
'w' | 寫入方式打開,將文件指針指向文件頭並將文件大小截爲零。如果文件不存在則嘗試創建之。 | |
'w+' | 讀寫方式打開,將文件指針指向文件頭並將文件大小截爲零。如果文件不存在則嘗試創建之。 | |
'a' | 寫入方式打開,將文件指針指向文件末尾。如果文件不存在則嘗試創建之。 | |
'a+' | 讀寫方式打開,將文件指針指向文件末尾。如果文件不存在則嘗試創建之。 | |
'x' | 創建並以寫入方式打開,將文件指針指向文件頭。如果文件已存在,則 fopen() 調用失敗並返回 FALSE,並生成一條 E_WARNING 級別的錯誤信息。如果文件不存在則嘗試創建之。這和給 底層的 open(2) 系統調用指定 O_EXCL | O_CREAT 標記是等價的。 |
'x+' | 創建並以讀寫方式打開,其他的行爲和 'x' 一樣。 | |
'c' | 只打開文件進行寫入。如果文件不存在,則創建該文件。如果它存在,它既不會被截斷(與“w”相反),也不會導致對該函數的調用失敗(與“x”一樣) | |
'c+' | 打開文件進行讀寫;否則它的行爲與“c”相同。 |
fread() 函數的第二個參數是每次要讀取的字節數,可以看到在測試代碼中我們是以 4 個字節爲單位進行讀取的,所以文件內容都是按 4 個字節分開的一行一行的輸出的。feof() 用於判斷當前文件的遊標指針是否已經移動到末尾了。
遊標操作
既然說到遊標,那麼我們就來看看遊標相關的操作。
while (!feof($f)) {
$contents = fread($f, 1024);
echo $contents, PHP_EOL;
}
//
rewind($f);
while (!feof($f)) {
$contents = fread($f, 1024);
echo $contents, PHP_EOL;
}
// Rain is falling all around,
// It falls on field and tree,
// It rains on the umbrella here,
// And on the ships at sea.
當使用最上方的代碼讀取過一遍內容後,遊標就已經到底了,這時候再次循環是無法讀取文件內容的,需要使用 rewind() 函數將遊標進行重置。
讀取單個字符
rewind($f);
while (($c = fgetc($f)) !== false) {
echo $c, PHP_EOL;
}
// R
// a
// i
// n
// i
// s
// f
// a
// ……
// ……
fgetc() 函數用於讀取單個字符。這個函數就比較簡單了,不過需要注意的是如果用它讀取中文的話,效果就不行了,因爲中文是一個字佔 2 或 3 個字節,使用這個函數讀取出來的將是亂碼的內容,在後面我們會有示例。
讀取一行
while (($c = fgets($f)) !== false) {
echo $c, PHP_EOL;
}
// Rain is falling all around,
// It falls on field and tree,
// It rains on the umbrella here,
// And on the ships at sea.
fgets() 函數用於按行讀取文件內容,這個函數也是比較常用的函數,大家不會陌生。
讀取 csv 文件
// fgetcsv
$f = fopen('./csv_test.csv', 'r');
while (($c = fgetcsv($f)) !== false) {
print_r($c);
}
// Array
// (
// [0] => 49
// [1] => 20
// [2] => 0
// [3] => 42
// [4] => 5/10/2020 12:32:18
// )
// Array
// (
// [0] => 50
// [1] => 21
// [2] => 0
// [3] => 74
// [4] => 5/10/2020 12:32:29
// )
// Array
// (
// [0] => 51
// [1] => 22
// [2] => 0
// [3] => 35.8
// [4] => 5/10/2020 12:32:38
// )
// ……
// ……
fclose($f);
關於 CSV 是什麼文件這裏就不多做解釋了,筆者畢業時的第一個項目中就有很多操作 CSV 文件的小功能,也可以說,這個 fgetcsv() 函數是筆者對於文件操作的啓蒙函數。它可以方便地按行讀取 CSV ,並將它們解析成數組格式方便我們地操作。不過一般如果是 Excel 文件轉換過來的內容,我們都會將第一行標題行排除掉,當然,這個就是根據業務開發的實際情況來說啦。
讀取過濾HTML
// fgetss
$f = fopen('./html_test.txt', 'r');
while (($c = fgetss($f)) !== false) {
echo $c, PHP_EOL;
}
// PHP Deprecated: Function fgetss() is deprecated
fclose($f);
fgetss() 函數在讀取文件的時候可以過濾掉 HTML 代碼,不過這個函數已經廢棄了。
中文讀取問題
對於中文的讀取來說,我們最主要關心的就是中文字符和英文字符所佔字節的區別問題,上面已經說過了,中文如果是 UTF8 編碼格式,將佔用 3 個字節,如果是 GBK 之類的將佔用 2 個字節。所以如果我們使用 fread() 時,要使用對應編碼的倍數來讀取,比如下面我們的測試文件是 UTF8 編碼的,需要按三個字符的方式讀取,就需要傳遞參數爲 6 。
// 中文測試
$f = fopen('./cn_test.txt', 'r+');
while (!feof($f)) {
$contents = fread($f, 6);
echo $contents, PHP_EOL;
}
// 我本
// 無爲
// 野客
// ,飄
// 飄浪
// 跡人
// 間。
// 一�
// �被�
// ……
// ……
while (!feof($f)) {
$contents = fread($f, 1024);
echo $contents, PHP_EOL;
}
//
rewind($f);
while (!feof($f)) {
$contents = fread($f, 1024);
echo $contents, PHP_EOL;
}
// 我本無爲野客,飄飄浪跡人間。
// 一時被命住名山。未免隨機應變。
// 識破塵勞擾擾,何如樂取清閒。
// 流霞細酌詠詩篇。且與白雲爲伴。
rewind($f);
while (($c = fgetc($f)) !== false) {
echo $c, PHP_EOL;
}
// �
// �
// �
// ……
// ……
rewind($f);
while (($c = fgets($f)) !== false) {
echo $c, PHP_EOL;
}
// 我本無爲野客,飄飄浪跡人間。
// 一時被命住名山。未免隨機應變。
// 識破塵勞擾擾,何如樂取清閒。
// 流霞細酌詠詩篇。且與白雲爲伴。
fclose($f);
fread() 函數讀取的內容中間爲什麼還會出現亂碼呢?因爲我們的換行符還是按英文碼只佔一個字節的呀!另外,fgetc() 函數就比較慘了,fgets() 函數還是能夠正常地讀取地。
讀取剩餘內容
$f = fopen('./cn_test.txt', 'r+');
echo fgets($f), PHP_EOL;
// 我本無爲野客,飄飄浪跡人間。
echo fpassthru($f), PHP_EOL;
// 一時被命住名山。未免隨機應變。
// 識破塵勞擾擾,何如樂取清閒。
// 流霞細酌詠詩篇。且與白雲爲伴。
rewind($f);
在這段測試代碼中,我們使用 fgets() 讀取了一行內容,然後再使用 fpassthru() 直接就將文件中剩餘的內容全部讀取出來了。這就是 fpassthru() 函數的作用,它可以將文件中游標之後全部剩餘的內容讀取出來。
fseek($f, 3 * 14 + 1);
echo fgets($f), PHP_EOL;
// 一時被命住名山。未免隨機應變。
另外還有一個 fseek() 函數,可以指定當前從哪個位置開始讀取,可以將它也看做是遊標操作的一部分。
rewind($f);
fseek($f, 14 * 2 * 3 + 1);
echo ftell($f), PHP_EOL; // 85
ftell() 函數則是返回的文件剩餘的字節信息。
文件句柄信息
print_r(fstat($f));
// Array
// (
// [0] => 16777220
// [1] => 8708492112
// [2] => 33188
// [3] => 1
// [4] => 501
// [5] => 20
// [6] => 0
// [7] => 177
// [8] => 1603414680
// [9] => 1603414679
// [10] => 1603414679
// [11] => 4096
// [12] => 8
// [dev] => 16777220
// [ino] => 8708492112
// [mode] => 33188
// [nlink] => 1
// [uid] => 501
// [gid] => 20
// [rdev] => 0
// [size] => 177
// [atime] => 1603414680
// [mtime] => 1603414679
// [ctime] => 1603414679
// [blksize] => 4096
// [blocks] => 8
// )
fstat() 函數和之前文章中我們講過的 stat() 函數的功能是一樣的,只不過它需要的是一個句柄參數,然後返回這個句柄對應文件的信息。而 stat() 直接給的是文件的路徑。
文件截斷
// 文件會變
ftruncate($f, 14*2*3+4);
echo fread($f, 8094), PHP_EOL;
// 我本無爲野客,飄飄浪跡人間。
// 一時被命住名山。未免隨機應變。
fclose($f);
ftruncate() 函數會從指定的位置截斷文件內容。在這裏我們只保留了前兩行的內容,後面的內容就被截斷掉了。使用這個函數需要注意的是,它會改變原有文件的內容。
讀取文件並從格式化輸入
$f = fopen("users_test.txt", "r");
while ($userinfo = fscanf($f, "%s\t%s\t%s\n")) {
print_r($userinfo);
}
// Array
// (
// [0] => javier
// [1] => argonaut
// [2] => pe
// )
// Array
// (
// [0] => hiroshi
// [1] => sculptor
// [2] => jp
// )
// Array
// (
// [0] => robert
// [1] => slacker
// [2] => us
// )
// Array
// (
// [0] => luigi
// [1] => florist
// [2] => it
// )
fclose($f);
fscanf() 函數會根據第二個參數傳遞的內容來格式化文件的內容。就像會用 printf() 函數一樣,只不過它是從讀取的角度來獲得數據內容。這裏會將製表符作爲分隔來形成格式化的結果數組。
文件內容匹配
var_dump(fnmatch('*fall[ing]*', file_get_contents('./test.txt'))); // bool(true)
fnmatch() 函數用於判斷給定的內容中是否包含第一個參數中指定的規則。它有點像正則表達式相關的函數的用法,而且並不是操作文件的,是針對字符串的。不過它的規則定義是以 Linux 系統中的文件操作匹配規則爲準的,也就是說它不是完全的正則規則。就像我們經常在 Linux 中查看某個文件的信息:ll *.txt 這樣。
進程文件讀取操作
這個是什麼意思呢?其實就是我們可以執行一段操作系統的進程代碼,然後獲得它的結果,這個流會以文件流的形式返回給 PHP 形成一個文件流句柄。
$handle = popen("/bin/ls", "r");
while(!feof($handle)){
echo fgets($handle);
}
pclose($handle);
// 1.PHP中的日期相關函數(三).php
// 2.學習PHP中的目錄操作.php
// 3.學習PHP中的高精度計時器HRTime擴展.php
// 4.PHP中DirectIO直操作文件擴展的使用.php
// 5.學習PHP中Fileinfo擴展的使用.php
// 6.PHP中的文件系統函數(一).php
// 7.PHP中的文件系統函數(二).php
// 8.PHP中的文件系統函數(三).php
// cn_test.txt
// csv_test.csv
// html_test.txt
// test.txt
// timg.jpeg
// users_test.txt
// write.txt
文件寫入
文件寫入就比較簡單了,就這麼一點代碼的介紹。而且也就三個函數。
// 寫入
$f = fopen('write.txt', 'w');
fwrite($f, "This is Test!\n");
fputs($f, "This is Test2!!\n");
$csv = [['id', 'name'],[1, 'Zyblog'], [2, '硬核項目經理']];
foreach($csv as $v){
fputcsv($f, $v);
}
fclose($f);
// This is Test!
// This is Test2!!
// id,name
// 1,Zyblog
// 2,硬核項目經理
fwrite() 用於向文件句柄中寫入內容。fputs() 是 fwrite() 的別名,它們兩個是一個東西。fputcsv() 函數則是以 CSV 的格式將數組內容寫入到文件中,它還有其它的參數可以修改分隔符具體使用哪個符號,在這裏我們默認就是逗號。
文件加鎖
$fp = fopen("/tmp/lock.txt", "w+");
if (flock($fp, LOCK_EX)) { // 進行排它型鎖定
fwrite($fp, "寫入數據:" . date('H:i:s') . "\n");
if(!$argv[1]){
sleep(50);
}
fflush($fp); // 釋放鎖之前刷新緩衝區
flock($fp, LOCK_UN); // 釋放鎖定
} else {
echo "無法獲得鎖,不能寫入!";
}
fclose($fp);
鎖定一個文件,然後其它的操作就不能讀取它了,這種操作一般在多線程或者多個功能會同時操作一個文件時會非常常用。flock() 的第二個參數可以設置讀鎖、寫鎖等,這裏我們使用的是 LOCK_EX 共享排它鎖,也就是一個寫鎖。當我們運行這段代碼後,在停留的時間內容,其它的腳本是無法寫入數據的,如果有同時操作這個文件的腳本在運行也會卡在這裏直到這邊的鎖釋放掉。
- LOCK_SH 取得共享鎖定(讀取的程序)。
- LOCK_EX 取得獨佔鎖定(寫入的程序。
- LOCK_UN 釋放鎖定(無論共享或獨佔)。
- 如果不希望 flock() 在鎖定時堵塞,則是 LOCK_NB(Windows 上還不支持)。
fflush() 用於刷新緩衝區,這個也是之前講過的關於 PHP 中緩衝區相關的知識,大家可以回去溫習一下,PHP中的輸出緩衝控制。在文件操作中,使用這個函數就能馬上刷新緩衝區的內容並將內容寫入到具體的文件中。
總結
是不是很嗨,一下子學習了這麼多函數。這篇文章結束也就是 PHP 原生的這些文件操作函數就學習完了。當然,瞭解只是一方面,更多的還是要多多嘗試應用到自己的項目中。
測試代碼:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202010/source/8.PHP中的文件系統函數(三).php
參考文檔:
https://www.php.net/manual/zh/ref.filesystem.php
各自媒體平臺均可搜索【硬核項目經理】