【详细】PHP操作TXT文件需要注意的事项

发布于 2020-04-20  2.7k 次阅读


本文于 2020年4月21日 8:41 更新,注意查看最新内容

前言

前两天写了个随机一句毒鸡汤API(以下简称毒鸡汤API),好像就昨天吧,好吧,时间不重要,但这是我除了刚学PHP的时候,用PHP操作文件之后,第一次用PHP操作TXT文件,期间遇到了很多的问题,这里做一个记录,以备日后回忆。

逐行读取TXT文件

/*
 * 逐行读取TXT文件 
 */
function getTxtcontent($txtfile){
  $file = @fopen($txtfile,'r');
  $content = array();
  if(!$file){
    return 'file open fail';
  } else{
    $i = 0;
    while (!feof($file)){
      $content[$i] = mb_convert_encoding(fgets($file),"UTF-8","GBK,ASCII,ANSI,UTF-8");
      $i++ ;
    }
    fclose($file);
    //数组去空
    $content = array_filter($content);
  }
  return $content;
}

这个Demo来自《PHP逐行读取txt文件的方法实例》,虽然我知道他们也是copy,但还是保留下出处。

不建议直接copy,因为在实际运用的时候会存在很多的问题。

如何过滤空行文本&为什么逐行读取同样内容,最后一行少两个字节

当时考虑到了空行的问题,加上原Demo又有做去空处理,于是测试了一番,这一测试,给自己挖了个大坑。

测试TXT文件

浏览器输出内容

首先,我发现原Demo的array_filter()去不了空行(第五行空行的值还在)。

这个方法我是第一次接触,查了一下文档,大概意思是该函数把输入数组中的每个键值传给回调函数,如果回调函数返回 true,则把输入数组中的当前键值返回给结果数组

判断了一下第五行的值,第五行 == true,于是明白array_filter()为什么去不掉它。

我看输出的时候什么都没有显示,但又占了两个字节,猜想是不是一个或者两个的空格。

尝试了str_replace(" ",' ',$content)preg_replace('# #','',$content),但“空格”就是去不掉。

想了各种各样的方法,还找到了遇到类似问题的帖子《如何删除数组中的空格或字符串?》,但都没有效果。


我开始反思,是不是一开始方向就错了,我找到老寒,向他说明情况,当时他在陪女朋友(真香)。

回我已经是第二天,把我喷的半死,说根本不是空格,是换行符。

我赶紧打开代码一顿操作:str_replace(array("\r\n", "\r", "\n"), "", $content),成了 :eek:

这是为什么呢,虽然功能实现了,但我依旧百思不得其解,输出的内容里面没有\r\n啊。


后来我在度娘找到了这篇文章《解析PHP处理换行符的问题 \r\n》,其中这样讲到:

在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33,Linux/Unix下的tty概念也来自于此)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这个字符将丢失。

于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。

后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。

Unix系统里,每行结尾只有“<换行>”,即"\n";Windows系统里面,每行结尾是“<换行><回车 >”,即“\n\r”;Mac系统里,每行结尾是“<回车>”,即"\n";。一个直接后果是,Unix/Mac系统下的文件在 Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

原来,在Windows系统里面(我的开发环境是Windows),每行结尾是“<换行><回车 >”,即“\n\r”。

而为什么即使用var_dump()都无法打印出换行符呢,我猜测是被浏览器解析了,所以才不在浏览器显示。

这也是为什么同样内容,最后一行少两个字节,因为最后一行没有换行,也就没有换行符。

在浏览器输出换行符

问题是知道,也解决了,但根本还是在于浏览器自动解析了换行符,那么有没有办法在浏览器把换行符打印出来呢。

我找了很久,但是都没有结果,后来突然灵机一动,用json_encode()方法输出成JSON格式,换行符就在浏览器显示出来了。

JSON格式输出(上面是图片)

这样就一目了然了,为什么之前去不掉 :!:

随机显示一条数据

这个API在设计的时候,是设计成随机显示一条数据。

之前用MySQL存储数据,可以用SELECT * FROM soul ORDER BY RAND() LIMIT 1随机查询一条数据,而现在换成了TXT存储。

俗话说,不懂就问,于是果断度娘,然后又查到一堆随机算法《数组的三种随机排序方法》

算法算是我比较薄弱的一方面,有点谈虎色变的意思,而且我认为,实现这样一个功能,没必要运用到复杂的算法。

后来就看到了array_rand()这个函数,它可以从数组中随机选择一个或多个元素。

只要在输出JSON的时候,使用array_rand()函数得到的下标,就能实现随机显示一条数据啦。

简单统计访问次数

写完API之后,想看看还有没有什么可以扩展的,需要想到了记录访问次数,简单一点的就是直接在页面输出一段专业网站的JS统计代码。

但我并不想这么做,我只想统计访问次数(之前没写过),越简单越好,于是我想到了之前的代码。

// 简单统计调用次数
$file = @fopen('visits.txt', 'r+');
if (!$file) {
  return 'file open fail';
} else {
  $visits = mb_convert_encoding(fgets($file), "UTF-8", "GBK,ASCII,ANSI,UTF-8");
}
fwrite($file,$visits + 1);
fclose($file);

如果你直接copy,你可能会被打死 :grin: ,因为我这里犯了一个错

fwrite()函数可以写入文件,但它是在原基础的情况上写入字符串。

也就是说,之前是个1,现在你写入个2到这个文件,之前的1并不会被替换掉。

度娘上方法各种各样,有的还极其复杂,直到我看见了《PHP替换txt文件第2行的数据》中的一个回答。

利用file_put_contents()重新生成文档变相替换原文内容,在我这种情况,简单又方便,下面是正确代码

// 简单统计调用次数
$file = @fopen('visits.txt', 'r+');
if (!$file) {
  return 'file open fail';
} else {
  $visits = mb_convert_encoding(fgets($file), "UTF-8", "GBK,ASCII,ANSI,UTF-8");
}
file_put_contents('visits.txt', $visits + 1);
fclose($file);

将SQL数据导出为TXT

这里我用的是Navicat,根据导出向导,选择文本文件,选择要导出的列(一般为需要的数据那一列,不需要导出id)。

导出之后,你就会发现数据被安排的整整齐齐。

TXT文件编码

2020年4月21日补充:昨天写完之后,突然想起文件编码的问题,于是决定做个补充。

之前测试API的时候,发现部分数据存在乱码,当时很是疑惑:首先我在页面头部就已经声明了字符集为UTF-8,其次我在取数据的时候,也对文本进行了转码(详见上面Demo),但依旧出现了乱码的情况。

后来经过排查,发现问题出在这一行代码:

$content[$i] = mb_convert_encoding(fgets($file),"UTF-8","GBK,ASCII,ANSI,UTF-8");

通过查看mb_convert_encoding()的官方文档,我发现该方法第三个参数不支持GBK编码和和ANSI编码(参见支持的编码)。

得出这样的一个结论,我感到十分的震惊,为什么?这是我得出这个结论之后最想问的一个问题。

那么既然不支持,便只能删掉,删掉之后再运行页面,输出数据显示正常。

PS:事实上这第三个参数是可选参数,如果没有提供,则会使用内部(internal)编码。

可能在效率上会有些许差别,但比起在转换前通过字符代码名称来指定,确实方便了不少,值得一用。

后话

一个简单的随机API,倒腾起来也花费了不少时间,正所谓生命在于折腾,我觉得值得。

如果遇到任何问题,欢迎在下方评论,大家一起学习,最后感谢诸多教程经验,谢谢!


这短短的一生,我们最终都会失去。