关于file_get_contents
我们都习惯使用 file_get_contents
函数来很方便的实现读取文件,远程资源访问。
file_get_contents ( string $filename , bool $use_include_path = false , resource $context = ? , int $offset = -1 , int $maxlen = ? ) : string
filename
要读取的文件的名称。
use_include_path
注意:
As of PHP 5 the FILE_USE_INCLUDE_PATH can be used to trigger include path search.
context
A valid context resource created with stream_context_create(). 如果你不需要自定义 context,可以用 null 来忽略。
offset
The offset where the reading starts on the original stream.
Seeking (offset) is not supported with remote files. Attempting to seek on non-local files may work with small offsets, but this is unpredictable because it works on the buffered stream.
maxlen
Maximum length of data read. The default is to read until end of file is reached. Note that this parameter is applied to the stream processed by the filters.
说到远程资源,就牵涉到服务端的协议类型,不同的类型自然有不同的请求方式,PHP内置fopen包装器
提供的协议有很多,当然你也可以自定义协议:
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流
所以PHP会分析你的地址,采用对应的协议去获取资源,这也是为什么file_get_contents
既可以访问本地资源,也可以访问远程资源。
有的协议需要带上参数或者身份信息,这就是所谓的上下文context
参数。
其实file_get_contents
的原理也很简单,就拿http请求为例:
file_get_contents("https://activity.book.com.cn/1615218508027YKwtdbTlMpOFHZFm8GVK.mp4");
建立tcp连接,接收数据,并存储在临时变量中,所以说它必须一次性接受完数据(内部也是分批read),之后才能处理,它的offset
参数不适用于远程资源,这个也好理解,如果是本地文件可以通过游标来实现偏移,但是对于远程资源,你必须从头接收这些数据,而无法选择跳过,你可能要说,那可以舍弃前面的只返回offset
之后的数据啊,实现起来当然没有问题,但是这样一来内部就必须是逐个字节来read,否则无法精确控制offset
的值,这样将大大降低性能,所以基于此,file_get_contents
的作者对offset
做了限制(只在读取本地资源时有效),但是maxlen
参数是有效的,这个也好理解,多余的就不读了呗。
了解了这些之后,我们就知道file_get_contents
读取远程资源的时候,内存占用较多,且比较浪费内存,当然,在请求小文件,请求接口的时候影响不大,如果是下载几十兆,几百兆的资源就不适合了。要知道网络数据传输实际上都是流式的,所以在读取的时候使用流式的读取效率会更高,这就是我们的 fread, fgets, fgetc, fgetss, stream_get_line
函数。
使用fread
比如将下载的文件存储到本地文件,使用流式处理。
$remotefile = fopen("https://activity.book.com.cn/1615218508027YKwtdbTlMpOFHZFm8GVK.mp4", 'r');
$fh = fopen($localfile, 'w');
while (!feof($remotefile)) {
$output = fread($remotefile, 8192);
fwrite($fh, $output);
}
fclose($remotefile);