int|false ftell ( resource $handle )
| 参数 | 描述 |
|---|---|
handle |
文件指针资源,通常由fopen()函数创建 |
false<?php
// 创建测试文件
$content = "Hello World!\nThis is a test file.\nPHP ftell() example.";
file_put_contents('test.txt', $content);
// 打开文件
$handle = fopen('test.txt', 'r');
if (!$handle) {
die('无法打开文件');
}
echo "文件内容:<br>" . nl2br(htmlspecialchars($content)) . "<br><br>";
// 初始位置
echo "初始位置: " . ftell($handle) . "<br>";
// 读取前5个字节
fread($handle, 5);
echo "读取5字节后位置: " . ftell($handle) . "<br>";
// 读取一行
fgets($handle);
echo "读取一行后位置: " . ftell($handle) . "<br>";
// 使用fseek移动指针
fseek($handle, 20);
echo "使用fseek移动到位置20: " . ftell($handle) . "<br>";
// 读取剩余内容
fread($handle, 10);
echo "再读取10字节后位置: " . ftell($handle) . "<br>";
// 移动到文件末尾
fseek($handle, 0, SEEK_END);
echo "移动到文件末尾: " . ftell($handle) . "<br>";
// 文件总大小
echo "文件总大小: " . filesize('test.txt') . " 字节<br>";
fclose($handle);
unlink('test.txt');
?>
<?php
// 创建大文件用于演示进度跟踪
$filename = 'large_file.txt';
if (!file_exists($filename)) {
$handle = fopen($filename, 'w');
for ($i = 0; $i < 1000; $i++) {
fwrite($handle, "Line $i: " . str_repeat('X', 100) . "\n");
}
fclose($handle);
}
// 读取文件并显示进度
function readWithProgress($filename, $chunkSize = 1024) {
$handle = fopen($filename, 'r');
if (!$handle) {
return false;
}
$totalSize = filesize($filename);
$bytesRead = 0;
echo "开始读取文件: $filename<br>";
echo "文件大小: " . number_format($totalSize) . " 字节<br>";
echo "<div class='progress mb-3' style='height: 20px;'>";
echo "<div class='progress-bar' id='progress-bar' role='progressbar' style='width: 0%' aria-valuenow='0' aria-valuemin='0' aria-valuemax='100'></div>";
echo "</div>";
while (!feof($handle)) {
$chunk = fread($handle, $chunkSize);
$bytesRead = ftell($handle);
// 计算进度百分比
$progress = ($totalSize > 0) ? round(($bytesRead / $totalSize) * 100) : 0;
// 显示进度(在实际应用中,可以通过AJAX更新进度条)
echo "<script>document.getElementById('progress-bar').style.width = '$progress%'; document.getElementById('progress-bar').innerHTML = '$progress%';</script>";
// 刷新输出缓冲区
flush();
// 模拟处理延迟
usleep(10000);
}
echo "<br>读取完成!总读取字节数: " . number_format($bytesRead) . "<br>";
fclose($handle);
return true;
}
// 使用示例
readWithProgress($filename, 2048);
// 清理文件(可选)
// unlink($filename);
?>
<?php
// 断点续传下载类
class ResumeDownload {
private $filename;
private $fileSize;
public function __construct($filename) {
$this->filename = $filename;
if (!file_exists($filename)) {
throw new Exception("文件不存在: $filename");
}
$this->fileSize = filesize($filename);
}
/**
* 处理下载请求
*/
public function download($downloadName = null) {
if ($downloadName === null) {
$downloadName = basename($this->filename);
}
// 获取已下载的字节数(断点位置)
$start = 0;
if (isset($_SERVER['HTTP_RANGE'])) {
if (preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches)) {
$start = (int)$matches[1];
$end = isset($matches[2]) ? (int)$matches[2] : $this->fileSize - 1;
if ($start < 0 || $start >= $this->fileSize || $end < $start) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
return false;
}
}
}
// 打开文件
$handle = fopen($this->filename, 'rb');
if (!$handle) {
throw new Exception("无法打开文件");
}
// 设置HTTP头部
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($downloadName) . '"');
if ($start > 0) {
// 断点续传
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . ($this->fileSize - 1) . '/' . $this->fileSize);
header('Content-Length: ' . ($this->fileSize - $start));
// 移动到断点位置
fseek($handle, $start);
} else {
// 全新下载
header('Content-Length: ' . $this->fileSize);
}
header('Accept-Ranges: bytes');
// 使用ftell跟踪当前位置
$chunkSize = 8192; // 8KB
$currentPosition = $start;
while (!feof($handle) && $currentPosition < $this->fileSize) {
// 检查连接是否仍然有效
if (connection_status() != 0) {
break;
}
$bytesToRead = min($chunkSize, $this->fileSize - $currentPosition);
$chunk = fread($handle, $bytesToRead);
echo $chunk;
flush();
// 更新当前位置
$currentPosition = ftell($handle);
// 可以在这里记录断点位置到数据库或文件
// $this->saveResumePoint($currentPosition);
}
fclose($handle);
return true;
}
/**
* 获取文件下载进度(模拟)
*/
public function getDownloadProgress($sessionId) {
// 在实际应用中,可以从数据库或session中获取已下载的字节数
$resumeFile = "resume_{$sessionId}.txt";
if (file_exists($resumeFile)) {
$downloaded = (int)file_get_contents($resumeFile);
return [
'downloaded' => $downloaded,
'total' => $this->fileSize,
'percentage' => $this->fileSize > 0 ? round(($downloaded / $this->fileSize) * 100, 2) : 0
];
}
return [
'downloaded' => 0,
'total' => $this->fileSize,
'percentage' => 0
];
}
}
// 使用示例
$filename = 'large_file.zip'; // 假设有一个大文件
// 模拟下载请求
if (isset($_GET['download'])) {
try {
$downloader = new ResumeDownload($filename);
$downloader->download();
exit;
} catch (Exception $e) {
echo "下载错误: " . $e->getMessage();
}
}
// 显示下载链接和进度
echo "<h5>断点续传下载示例:</h5>";
echo "<a href='?download=1' class='btn btn-primary'>下载大文件</a><br><br>";
// 模拟显示下载进度
try {
$downloader = new ResumeDownload($filename);
$progress = $downloader->getDownloadProgress('test_session');
echo "下载进度: {$progress['percentage']}%<br>";
echo "已下载: " . number_format($progress['downloaded']) . " / " . number_format($progress['total']) . " 字节<br>";
echo "<div class='progress mt-2' style='height: 20px;'>";
echo "<div class='progress-bar' role='progressbar' style='width: {$progress['percentage']}%'>{$progress['percentage']}%</div>";
echo "</div>";
} catch (Exception $e) {
echo "文件信息获取失败: " . $e->getMessage();
}
?>
<?php
class FilePositionTracker {
private $handle;
private $filename;
private $positions = []; // 记录关键位置
public function __construct($filename, $mode = 'r') {
$this->filename = $filename;
$this->handle = fopen($filename, $mode);
if (!$this->handle) {
throw new Exception("无法打开文件: {$filename}");
}
// 记录文件开头位置
$this->positions['start'] = ftell($this->handle);
}
/**
* 获取当前位置
*/
public function getPosition() {
return ftell($this->handle);
}
/**
* 标记当前位置
*/
public function mark($name) {
$this->positions[$name] = $this->getPosition();
return $this;
}
/**
* 返回到标记的位置
*/
public function returnTo($name) {
if (isset($this->positions[$name])) {
return fseek($this->handle, $this->positions[$name]);
}
return false;
}
/**
* 获取所有标记的位置
*/
public function getMarks() {
return $this->positions;
}
/**
* 读取从当前位置到下一个标记的内容
*/
public function readToNextMark($markName) {
$start = $this->getPosition();
if (!isset($this->positions[$markName])) {
throw new Exception("标记不存在: {$markName}");
}
$end = $this->positions[$markName];
if ($end <= $start) {
return ''; // 标记在当前位置之前
}
$length = $end - $start;
return fread($this->handle, $length);
}
/**
* 在文件中搜索字符串并标记位置
*/
public function searchAndMark($search, $markName) {
$current = $this->getPosition();
$found = false;
while (!feof($this->handle)) {
$line = fgets($this->handle);
if (strpos($line, $search) !== false) {
// 找到字符串,记录当前位置
$this->mark($markName);
$found = true;
break;
}
}
// 恢复原始位置
fseek($this->handle, $current);
return $found;
}
/**
* 获取文件大小
*/
public function getFileSize() {
$current = $this->getPosition();
fseek($this->handle, 0, SEEK_END);
$size = ftell($this->handle);
fseek($this->handle, $current);
return $size;
}
/**
* 计算剩余字节数
*/
public function getRemainingBytes() {
$current = $this->getPosition();
fseek($this->handle, 0, SEEK_END);
$end = ftell($this->handle);
fseek($this->handle, $current);
return $end - $current;
}
/**
* 关闭文件
*/
public function close() {
if ($this->handle) {
fclose($this->handle);
$this->handle = null;
}
}
public function __destruct() {
$this->close();
}
}
// 使用示例
try {
// 创建测试文件
$testContent = "=== Section 1 ===\nContent of section 1.\n=== Section 2 ===\nContent of section 2.\n=== Section 3 ===\nContent of section 3.\n";
file_put_contents('sections.txt', $testContent);
// 创建跟踪器
$tracker = new FilePositionTracker('sections.txt', 'r');
echo "<h5>文件位置跟踪示例:</h5>";
// 搜索并标记各个部分
$tracker->searchAndMark('=== Section 1 ===', 'section1_start');
$tracker->searchAndMark('=== Section 2 ===', 'section2_start');
$tracker->searchAndMark('=== Section 3 ===', 'section3_start');
// 获取文件大小
echo "文件大小: " . $tracker->getFileSize() . " 字节<br>";
// 返回到第一个标记并读取内容
$tracker->returnTo('section1_start');
fgets($tracker->handle); // 跳过标记行
$section1 = $tracker->readToNextMark('section2_start');
echo "Section 1 内容: " . htmlspecialchars(trim($section1)) . "<br><br>";
// 返回到第二个标记
$tracker->returnTo('section2_start');
fgets($tracker->handle);
$section2 = $tracker->readToNextMark('section3_start');
echo "Section 2 内容: " . htmlspecialchars(trim($section2)) . "<br><br>";
// 获取所有标记位置
$marks = $tracker->getMarks();
echo "<h6>标记位置:</h6>";
echo "<pre>" . print_r($marks, true) . "</pre>";
// 计算剩余字节
echo "剩余字节数: " . $tracker->getRemainingBytes() . "<br>";
$tracker->close();
// 清理
unlink('sections.txt');
} catch (Exception $e) {
echo "错误: " . $e->getMessage();
}
?>
fseek()配合使用,实现文件指针的精确定位| 特性 | ftell() | fseek() |
|---|---|---|
| 功能 | 获取当前位置 | 设置当前位置 |
| 参数 | 只需要文件句柄 | 需要文件句柄、偏移量和参考位置 |
| 返回值 | 当前位置(字节数)或false | 0成功,-1失败 |
| 使用场景 | 跟踪读取进度、断点续传 | 随机访问、跳转到特定位置 |
| 配合使用 | 通常与fseek()配合使用 | 通常与ftell()配合使用 |
fseek() - 在文件指针中定位rewind() - 倒回文件指针的位置feof() - 测试文件指针是否到达文件末尾fopen() - 打开文件或URLfread() - 读取文件(二进制安全)fwrite() - 写入文件filesize() - 获取文件大小stat() - 获取文件信息记录文件下载/上传的断点位置,实现中断后继续传输。
显示文件读取、处理或传输的实时进度。
在解析复杂文件格式时,跟踪当前位置以便错误恢复。
记录上次读取日志文件的位置,实现增量读取。