PHP tmpfile() 函数

定义和用法

tmpfile() 函数用于创建一个具有唯一文件名的临时文件,并返回一个文件句柄。该临时文件会在关闭(使用 fclose())或脚本结束时自动删除。

这个函数非常适合需要临时存储数据但不需要持久化保存的场景。由于文件会自动清理,减少了忘记删除临时文件导致磁盘空间浪费的风险。

注意: tmpfile() 创建的临时文件在系统临时目录中,通常位于 /tmp(Unix/Linux)或系统指定的临时目录(Windows)。

语法

tmpfile ( ) : resource|false

参数说明:

  • 该函数没有参数
  • 返回一个文件句柄(resource),可用于文件操作函数如 fwrite()fread()

返回值

  • 成功时:返回一个文件句柄(resource)
  • 失败时:返回 false,通常是由于以下原因:
    • 无法创建临时文件
    • 临时目录不可写
    • 磁盘空间不足
    • 达到系统文件打开限制
重要: 返回的文件句柄是以读写模式(w+)打开的,文件指针位于文件开头。文件会在调用 fclose() 或脚本结束时自动删除。

示例

示例 1:基本用法

创建临时文件并写入/读取数据:

<?php
// 创建临时文件
$tempHandle = tmpfile();

if ($tempHandle !== false) {
    echo "临时文件创建成功<br>";

    // 写入数据
    fwrite($tempHandle, "这是临时文件内容\n");
    fwrite($tempHandle, "第二行数据\n");

    // 重置文件指针到开头
    rewind($tempHandle);

    // 读取数据
    echo "文件内容:<br>";
    while (!feof($tempHandle)) {
        echo fgets($tempHandle) . "<br>";
    }

    // 获取文件信息
    $fileStats = fstat($tempHandle);
    echo "文件大小:" . $fileStats['size'] . " 字节<br>";

    // 关闭文件(会自动删除)
    fclose($tempHandle);
    echo "文件已关闭并自动删除";
} else {
    echo "无法创建临时文件";
}
?>
示例 2:处理大文件数据

使用临时文件处理大量数据:

<?php
// 模拟处理大量数据
function processLargeData($dataGenerator) {
    // 创建临时文件
    $tempHandle = tmpfile();
    if ($tempHandle === false) {
        throw new RuntimeException('无法创建临时文件');
    }

    $totalBytes = 0;

    // 写入生成的数据
    foreach ($dataGenerator as $chunk) {
        $bytesWritten = fwrite($tempHandle, $chunk);
        $totalBytes += $bytesWritten;
    }

    // 处理完成后,重置指针并读取
    rewind($tempHandle);

    $results = [];
    while (!feof($tempHandle)) {
        $line = fgets($tempHandle);
        if ($line !== false) {
            $results[] = trim($line);
        }
    }

    // 获取文件统计信息
    $stats = fstat($tempHandle);

    // 关闭文件(自动删除)
    fclose($tempHandle);

    return [
        'results' => $results,
        'total_bytes' => $totalBytes,
        'file_size' => $stats['size'],
        'line_count' => count($results)
    ];
}

// 模拟数据生成器
function generateData($lines = 1000) {
    for ($i = 0; $i < $lines; $i++) {
        yield "数据行 #" . ($i + 1) . ": " . uniqid() . "\n";
    }
}

// 使用示例
try {
    $result = processLargeData(generateData(500));
    echo "处理完成!<br>";
    echo "写入字节数:" . $result['total_bytes'] . "<br>";
    echo "文件大小:" . $result['file_size'] . " 字节<br>";
    echo "行数:" . $result['line_count'] . "<br>";
    echo "临时文件已自动清理";
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>
示例 3:二进制数据操作

使用临时文件处理二进制数据:

<?php
// 创建临时文件处理二进制数据
$tempHandle = tmpfile();

if ($tempHandle) {
    // 写入二进制数据
    $binaryData = pack('C*', 0x48, 0x65, 0x6C, 0x6C, 0x6F); // "Hello" 的二进制
    fwrite($tempHandle, $binaryData);

    // 重置指针
    rewind($tempHandle);

    // 读取二进制数据
    $readData = fread($tempHandle, 1024);

    // 转换为十六进制显示
    $hexData = '';
    for ($i = 0; $i < strlen($readData); $i++) {
        $hexData .= sprintf('%02X ', ord($readData[$i]));
    }

    echo "二进制数据(十六进制):" . trim($hexData) . "<br>";
    echo "文本表示:" . $readData . "<br>";

    // 也可以使用bin2hex
    echo "使用bin2hex:" . bin2hex($readData) . "<br>";

    fclose($tempHandle);
}
?>
示例 4:CSV数据处理

使用临时文件处理CSV数据:

<?php
function processCSVData($csvRows) {
    $tempHandle = tmpfile();
    if (!$tempHandle) {
        return false;
    }

    // 写入CSV标题
    fputcsv($tempHandle, ['ID', 'Name', 'Email', 'Score']);

    // 写入数据行
    foreach ($csvRows as $row) {
        fputcsv($tempHandle, $row);
    }

    // 重置指针并读取处理
    rewind($tempHandle);

    $processed = [];
    $isHeader = true;
    $header = [];

    while (($row = fgetcsv($tempHandle)) !== false) {
        if ($isHeader) {
            $header = $row;
            $isHeader = false;
            continue;
        }

        // 处理数据:例如,只保留分数大于60的行
        if (isset($row[3]) && $row[3] > 60) {
            $processed[] = array_combine($header, $row);
        }
    }

    // 如果需要,可以将处理后的数据写回临时文件
    rewind($tempHandle);
    ftruncate($tempHandle, 0); // 清空文件

    fputcsv($tempHandle, $header);
    foreach ($processed as $row) {
        fputcsv($tempHandle, array_values($row));
    }

    // 获取最终结果
    rewind($tempHandle);
    $finalCSV = stream_get_contents($tempHandle);

    fclose($tempHandle);

    return [
        'processed_count' => count($processed),
        'csv_data' => $finalCSV
    ];
}

// 使用示例
$csvData = [
    [1, '张三', 'zhangsan@example.com', 85],
    [2, '李四', 'lisi@example.com', 45],
    [3, '王五', 'wangwu@example.com', 92],
    [4, '赵六', 'zhaoliu@example.com', 58]
];

$result = processCSVData($csvData);
if ($result) {
    echo "处理完成!保留 " . $result['processed_count'] . " 条记录<br>";
    echo "<pre>" . htmlspecialchars($result['csv_data']) . "</pre>";
}
?>
示例 5:错误处理和资源管理

确保临时文件被正确关闭:

<?php
// 使用try-finally确保文件关闭
function withTempFile($callback) {
    $tempHandle = tmpfile();
    if ($tempHandle === false) {
        throw new RuntimeException('无法创建临时文件');
    }

    try {
        return $callback($tempHandle);
    } finally {
        // 无论是否发生异常,都会关闭文件
        if (is_resource($tempHandle)) {
            fclose($tempHandle);
        }
    }
}

// 使用示例
try {
    $result = withTempFile(function($handle) {
        // 写入大量数据
        for ($i = 0; $i < 10000; $i++) {
            fwrite($handle, "Line $i: " . uniqid() . "\n");
        }

        // 模拟可能出错的操作
        if (rand(0, 10) === 0) {
            throw new Exception('模拟的随机错误');
        }

        // 返回处理结果
        rewind($handle);
        $lineCount = 0;
        while (!feof($handle)) {
            fgets($handle);
            $lineCount++;
        }

        return ['line_count' => $lineCount];
    });

    echo "处理成功,行数:" . $result['line_count'] . "<br>";
    echo "临时文件已自动清理";
} catch (Exception $e) {
    echo "发生错误:" . $e->getMessage() . "<br>";
    echo "临时文件已自动清理";
}
?>

tmpfile() 与 tempnam() 对比

特性 tmpfile() tempnam()
函数签名 tmpfile(): resource|false tempnam(dir, prefix): string|false
返回值 文件句柄(resource) 文件路径(string)
文件打开模式 w+(读写模式) 不打开文件,只创建空文件
自动删除 是(关闭时或脚本结束时) 否(需要手动 unlink()
文件位置 系统临时目录 可以指定目录
文件名前缀 系统生成,不可控 可以指定前缀
使用场景 临时数据处理,不需要保留文件 需要文件路径,或需要自定义目录/前缀
安全性 较高(自动清理) 较低(需要手动清理)
代码示例对比
<?php
// tmpfile() 示例
echo "tmpfile() 示例:<br>";
$tmpfileHandle = tmpfile();
fwrite($tmpfileHandle, "tmpfile数据");
rewind($tmpfileHandle);
echo "内容:" . fread($tmpfileHandle, 1024) . "<br>";
// 文件会在fclose()或脚本结束时自动删除

echo "<br>tempnam() 示例:<br>";
$tempnamPath = tempnam(sys_get_temp_dir(), 'myprefix_');
echo "文件路径:" . $tempnamPath . "<br>";
file_put_contents($tempnamPath, "tempnam数据");
echo "内容:" . file_get_contents($tempnamPath) . "<br>";
// 需要手动删除
unlink($tempnamPath);
echo "文件已手动删除";

fclose($tmpfileHandle); // tmpfile文件自动删除
?>

自动清理机制

工作原理

tmpfile() 创建的临时文件会在以下情况下自动删除:

清理时机 描述
fclose() 调用时 显式关闭文件句柄时立即删除
脚本正常结束时 PHP脚本执行完成后自动清理
脚本异常终止时 即使脚本因错误或异常终止,文件通常也会被清理
资源被垃圾回收时 当文件句柄变量超出作用域且被PHP垃圾回收时
演示脚本
<?php
// 演示自动清理机制
function demonstrateCleanup() {
    echo "创建临时文件...<br>";
    $handle = tmpfile();

    if ($handle) {
        $meta = stream_get_meta_data($handle);
        echo "临时文件路径:" . $meta['uri'] . "<br>";

        fwrite($handle, "测试数据");
        echo "已写入数据<br>";

        // 检查文件是否存在
        clearstatcache();
        echo "文件存在:" . (file_exists($meta['uri']) ? '是' : '否') . "<br>";

        // 不调用 fclose(),让脚本结束时自动清理
        echo "不显式关闭文件句柄...<br>";
        // 文件会在函数返回、变量销毁或脚本结束时自动清理
    }
}

demonstrateCleanup();

// 此时临时文件可能还未被清理,因为句柄变量仍在作用域中
echo "<br>函数已返回,但文件句柄变量仍在作用域中<br>";

// 显式清理
echo "<br>现在显式清理所有tmpfile资源:<br>";

// 创建多个临时文件但不关闭
$handles = [];
for ($i = 0; $i < 3; $i++) {
    $handle = tmpfile();
    if ($handle) {
        $meta = stream_get_meta_data($handle);
        echo "创建临时文件 #" . ($i + 1) . ": " . $meta['uri'] . "<br>";
        $handles[] = $handle;
    }
}

// 显式关闭所有句柄
foreach ($handles as $handle) {
    fclose($handle); // 立即删除文件
}

echo "所有临时文件已显式关闭并删除<br>";
?>

注意事项和最佳实践

重要提示
  • 文件模式: tmpfile() 以 w+ 模式打开文件,文件指针位于开头
  • 二进制安全: 默认以二进制模式打开,适合处理二进制数据
  • 文件位置: 无法控制文件创建的具体位置,总是在系统临时目录
  • 资源管理: 虽然文件会自动清理,但最好显式调用 fclose()
  • 大文件处理: 临时文件占用磁盘空间,处理大文件时需确保有足够空间
  • 并发访问: 临时文件是进程私有的,不会与其他进程冲突
最佳实践示例
<?php
// 1. 总是检查返回值
$handle = tmpfile();
if ($handle === false) {
    // 处理错误
    throw new RuntimeException('无法创建临时文件');
}

// 2. 使用try-finally确保清理
try {
    // 使用临时文件
    fwrite($handle, "数据");
    rewind($handle);
    // ... 其他操作
} finally {
    if (is_resource($handle)) {
        fclose($handle);
    }
}

// 3. 处理二进制数据时使用二进制函数
function processBinaryData() {
    $handle = tmpfile();
    if (!$handle) return false;

    // 写入二进制数据
    $binary = random_bytes(1024); // 生成随机二进制数据
    fwrite($handle, $binary);

    // 读取时使用fread而不是文本函数
    rewind($handle);
    $data = fread($handle, 1024);

    fclose($handle);
    return $data;
}

// 4. 监控资源使用
function monitorTempfileUsage() {
    $startMemory = memory_get_usage();
    $startTime = microtime(true);

    $handle = tmpfile();
    fwrite($handle, str_repeat('x', 1024 * 1024)); // 写入1MB数据

    $peakMemory = memory_get_peak_usage();
    $endTime = microtime(true);

    fclose($handle);

    return [
        'memory_used' => $peakMemory - $startMemory,
        'time_elapsed' => $endTime - $startTime
    ];
}
?>
常见错误
  • 忘记检查返回值: 假设 tmpfile() 总是成功
  • 多次调用 fclose(): 对已关闭的句柄再次调用 fclose() 会导致错误
  • 依赖自动清理: 在长期运行的脚本中,不关闭临时文件可能导致资源泄漏
  • 文件路径访问: 试图通过路径访问 tmpfile() 创建的文件(应始终使用返回的句柄)
  • 并发写入: 在多线程/多进程环境中,tmpfile() 是安全的,但需要正确管理句柄

实际应用场景

场景1:数据转换中间存储
<?php
class DataConverter {
    public function convertFormat($inputData, $fromFormat, $toFormat) {
        // 创建临时文件存储原始数据
        $tempHandle = tmpfile();
        if (!$tempHandle) {
            throw new RuntimeException('无法创建临时文件');
        }

        try {
            // 写入原始数据
            if ($fromFormat === 'json') {
                fwrite($tempHandle, json_encode($inputData));
            } elseif ($fromFormat === 'csv') {
                foreach ($inputData as $row) {
                    fputcsv($tempHandle, $row);
                }
            } else {
                fwrite($tempHandle, serialize($inputData));
            }

            // 转换处理
            rewind($tempHandle);
            $converted = $this->performConversion($tempHandle, $fromFormat, $toFormat);

            return $converted;
        } finally {
            fclose($tempHandle);
        }
    }

    private function performConversion($handle, $fromFormat, $toFormat) {
        // 根据格式进行转换
        if ($fromFormat === 'json' && $toFormat === 'array') {
            rewind($handle);
            $json = stream_get_contents($handle);
            return json_decode($json, true);
        }

        // 其他转换逻辑...
        return null;
    }
}

// 使用示例
$converter = new DataConverter();
$data = [
    ['name' => '张三', 'age' => 25],
    ['name' => '李四', 'age' => 30]
];

$result = $converter->convertFormat($data, 'array', 'json');
echo "转换结果:" . json_encode($result);
?>
场景2:图像处理缓存
<?php
class ImageProcessor {
    public function applyFilter($imagePath, $filterType) {
        // 加载图像
        $image = imagecreatefromjpeg($imagePath);
        if (!$image) {
            throw new RuntimeException('无法加载图像');
        }

        // 创建临时文件存储处理结果
        $tempHandle = tmpfile();
        if (!$tempHandle) {
            imagedestroy($image);
            throw new RuntimeException('无法创建临时文件');
        }

        try {
            // 应用滤镜
            if ($filterType === 'grayscale') {
                imagefilter($image, IMG_FILTER_GRAYSCALE);
            } elseif ($filterType === 'blur') {
                imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR);
            }

            // 将图像写入临时文件
            imagejpeg($image, stream_get_meta_data($tempHandle)['uri'], 90);

            // 将临时文件内容读入内存(或直接输出)
            rewind($tempHandle);
            $imageData = stream_get_contents($tempHandle);

            return [
                'data' => $imageData,
                'size' => strlen($imageData),
                'type' => 'image/jpeg'
            ];
        } finally {
            // 清理资源
            imagedestroy($image);
            fclose($tempHandle);
        }
    }
}

// 使用示例
$processor = new ImageProcessor();
try {
    $result = $processor->applyFilter('input.jpg', 'grayscale');
    echo "图像处理完成,大小:" . $result['size'] . " 字节";
    // 可以将 $result['data'] 保存到文件或直接输出
} catch (Exception $e) {
    echo "错误:" . $e->getMessage();
}
?>