FileCache.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. /**
  3. * 文件缓存类
  4. * 支持数据缓存和片段缓存
  5. *
  6. */
  7. class FileCache
  8. {
  9. /**
  10. * 缓存文件存放路径
  11. */
  12. public $path;
  13. /**
  14. * 缓存存放目录数
  15. */
  16. public $max_path;
  17. /**
  18. * 最多缓存多少个文件,按需配置,此值越大,GC消耗时间越久
  19. * 此值对cache命中率影响非常小,几乎可以忽略
  20. */
  21. public $max_file;
  22. /**
  23. * GC 执行概率 百万分之 *
  24. */
  25. public $gc_probality;
  26. private $basepath;
  27. public function __construct($path = "cache", $max_path = 100, $max_file = 50000, $gc_probality = 100)
  28. {
  29. $this->path = $path;
  30. $this->max_path = $max_path;
  31. $this->max_file = $max_file;
  32. $this->gc_probality = $gc_probality;
  33. $this->basepath = realpath($this->path) . DIRECTORY_SEPARATOR;
  34. }
  35. /**
  36. * 设置缓存
  37. *
  38. * @param string $key 保存的key,操作数据的唯一标识,不可重复
  39. * @param value $val 数据内容,可以是int/string/array/object/Boolean 其他没测过,如有需求自行测试
  40. * @param int $expired 过期时间,不设默认为一年
  41. * @return bool
  42. */
  43. public function set($key, $val, $expired = 31536000)
  44. {
  45. if (rand(0, 1000000) < $this->gc_probality) $this->gc();
  46. $key = strval($key);
  47. $cache_file = $this->_getCacheFile($key);
  48. $data = unserialize(file_get_contents($cache_file));
  49. empty($data[$key]) && $data[$key] = array();
  50. $data[$key]['data'] = $val;
  51. $data[$key]['expired'] = time() + $expired;
  52. file_put_contents($cache_file, serialize($data), LOCK_EX) or die("写入文件失败");
  53. return @touch($cache_file, $data[$key]['expired']);
  54. }
  55. /**
  56. * 获得保存的缓存
  57. *
  58. * @param string $key key,操作数据的唯一标识
  59. * @return null/data
  60. */
  61. public function get($key)
  62. {
  63. $key = strval($key);
  64. $cache_file = $this->_getCacheFile($key);
  65. $val = @file_get_contents($cache_file);
  66. if (!empty($val))
  67. {
  68. $val = unserialize($val);
  69. if (!empty($val) && isset($val[$key]))
  70. {
  71. $data = (array) $val[$key];
  72. if ($data['expired'] < time())
  73. {
  74. $this->delete($key);
  75. return null;
  76. }
  77. return $data['data'];
  78. }
  79. }
  80. return null;
  81. }
  82. /**
  83. * 开始片段缓存
  84. * 必须配合endCache使用
  85. *
  86. * @param string $key 保存的key,操作数据的唯一标识,不可重复
  87. * @param int $expired 过期时间,不设默认为一年
  88. * @return bool
  89. */
  90. public function startCache($key, $expired = 31536000)
  91. {
  92. $data = $this->get($key);
  93. if (!empty($data))
  94. {
  95. print $data;
  96. return false;
  97. }
  98. else
  99. {
  100. ob_start();
  101. print $key . "||" . $expired . "filecache_used::]";
  102. return true;
  103. }
  104. }
  105. /**
  106. * 结束片段缓存
  107. *
  108. * @return bool
  109. */
  110. public function endCache()
  111. {
  112. $data = ob_get_contents();
  113. ob_end_clean();
  114. preg_match("/(.*?)filecache_used::]/is", $data, $key);
  115. if (empty($key[1]))
  116. {
  117. return null;
  118. }
  119. $data = str_replace($key[0], '', $data);
  120. $t = explode("||", $key[1]);
  121. $key = $t[0];
  122. $expired = $t[1];
  123. $this->set($key, $data, $expired);
  124. print $data;
  125. }
  126. /**
  127. * 删除缓存
  128. *
  129. * @param string $key 保存的key,操作数据的唯一标识,不可重复
  130. * @return bool
  131. */
  132. public function delete($key)
  133. {
  134. $key = strval($key);
  135. $cache_file = $this->_getCacheFile($key);
  136. $data = unserialize(file_get_contents($cache_file));
  137. unset($data[$key]);
  138. if (empty($data))
  139. {
  140. return @unlink($cache_file);
  141. }
  142. file_put_contents($cache_file, serialize($data), LOCK_EX);
  143. return true;
  144. }
  145. /**
  146. * 缓存回收机制
  147. * 遍历所有缓存文件,删除已过期文件,如果缓存文件存在不止一个缓存数据,照删不务……
  148. * TODO 这里之前用hashtable做了数据索引,GC时间会比遍历快30%左右,但是会拖慢set和get的时间,据我测试set会慢一倍!
  149. *
  150. * @param string $path 缓存目录
  151. * @return void
  152. */
  153. public function gc($path = null)
  154. {
  155. if($path === null) $path = $this->basepath;
  156. if(($handle = opendir($path)) === false) return;
  157. while(($file = readdir($handle)) !== false)
  158. {
  159. if($file[0] === '.') continue;
  160. $fullPath = $path . DIRECTORY_SEPARATOR . $file;
  161. if(is_dir($fullPath))
  162. {
  163. $this->gc($fullPath);
  164. }
  165. elseif(@filemtime($fullPath) < time())
  166. {
  167. @unlink($fullPath);
  168. }
  169. }
  170. closedir($handle);
  171. }
  172. private function _getCacheFile($key)
  173. {
  174. $hash = $this->hash32($key);
  175. $path = $this->basepath . $this->_getPathName($hash);
  176. $file = $path . DIRECTORY_SEPARATOR . $this->_getCacheFileName($hash);
  177. if (!file_exists($path))
  178. {
  179. mkdir($path, 0777);
  180. }
  181. if (!file_exists($file))
  182. {
  183. $handler = fopen($file, 'w');
  184. fclose($handler);
  185. }
  186. return $file;
  187. }
  188. private function _getPathName($hash)
  189. {
  190. return $hash % $this->max_path;
  191. }
  192. private function _getCacheFileName($hash)
  193. {
  194. return $hash % $this->max_file;
  195. }
  196. private function hash32($str)
  197. {
  198. return crc32($str) >> 16 & 0x7FFFFFFF;
  199. }
  200. }