前些阵子写过一个针对地区配置文件的优化,今天有同事提起,特写下来共同交流!
问题描述:部分应用需要展示多个企业的地区信息,但从数据库或缓存读到的是一堆地区ID,无法直接显示,需要读地区配置文件将ID转换为地区名; 而地区配置(area.php)文件较大(122K),每次载入文件会耗费7ms左右时间(正常SQL读数据库才耗费不到1ms时间,这个耗费5ms,简直无法忍受)
解决办法:将地区配置的php文件转换为纯文本文件,每次只读需要的行,不用读全部行;时间复杂度为:O(1)
使用到的函数: fopen,fseek, fread
具体的实现过程:
第一步:将地区配置文件转换为文本文件
1:将省、市、区的关联数组补全,如市的数组为:
array(‘1‘ => ‘上海市‘, ‘3‘ => ‘北京市‘, ‘4‘ => ‘郑州‘)
补全后变为:
array(‘1‘ => ‘上海市‘, ‘2‘ => ‘‘, ‘3‘ => ‘北京市‘, ‘4‘ => ‘郑州‘)
2:将省市区的名称依次写入文本、一行一个
书写的格式:
行号 省、市、区名 1 北京 2 上海 3 天津 4 重庆 5 香港 . . . . . . 35 上海 36 37 北京 38 郑州 . . . . 753 ID为1的县名称 754 ID为2的县名称 755 ID为3的县名称 . . . . . .
此时省、市、区的ID与行号就会有如下的关联:
省:lineid = ID 市:lineid = ID + 34 区:lineid = ID + 34 + 718
至此,我们可以根据一个省、市、区的ID获取一个行号,然后用fread读这一行就可以获取到想要的结果;
第二步:优化第一步结构,并从文本中读数据
完成第一步之后,就开始翻手册来查找读文件指定行内容的函数,找了很久,遗憾的发现竟然没有这个函数,那真是万念俱灰呀,又瞎搞了。
一袋烟功夫后,终于想明白了,文件嘛,肯定是存磁盘的,你让它读第100行的内容,它也不知道100行的磁盘"偏移量"是多少,不能一下子读出来也是理所当然;至此,突然恍然大悟,如果第100行的磁盘“偏移量”是固定值,那么直接读出来应该不是问题。哈,很简单嘛,把每行的字节数设为固定值(假设每行48个字节,不足48的填空白字符),第N行的“偏移量”也就固定了。如:读100行的的内容,直接用fseek函数将文件内部指针移动到100 * 48位置,然后用fread函数读一行内容即可。
功能已基本搞定;使用到的函数有:fopen,fseek,fread;
第三步:程序的几点优化
- 字符写文件时,使用pack函数将字符转为固定字节长度;对每行写入的内容优化:省名称行不变,市名称行除存储市名称外追加所属省的信息,县名称行追加市、省信息;此时,省名称行固定长度48字节,市行固定长度96,区行固定长度144;将省、市、区各部分每行的长度,省、市、区的数量信息存到文件的第一行;查询的类使用单例模式,对查询过的ID存储防止重复查询;
文件存储格式变为:
0 第一行存储基本信息,如省的数量,省名称每行占的字节数等; 1 北京******* 2 上海******* 3 天津******* 4 重庆******* 5 香港******* . . . . . . 35 上海,上海***************** 36 ************************* 37 北京,北京***************** 38 郑州,河南***************** . . . . . . 753 ID为1的县名称,ID为1县所在市,所在省名称*********** 754 ID为2的县名称,ID为2县所在市,所在省名称*********** 755 ID为3的县名称,ID为3县所在市,所在省名称*********** . . . . . .
性能测试
代码:
<?php include"Area.php"; //随机生成20个ID for($i = 1; $i < 20000; $i++) { $id[] = rand(1,3000); } $stime = microtime(true); foreach($id as $v) { $info = Xz_Area::getInstance()->getAreaNav($v, ‘area‘); } $etime = microtime(true); echo "\n spend time:". (($etime - $stime) * 1000)."ms\n";
结果:普通硬盘平均执行时间为0.5ms; Ssd盘平均执行时间在0.3ms以内