PHP中的文件系统函数(三)

总算来到我们最关心的部分了,也就是 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

‘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

Related Post

发表回复