在php开发中,文件差异比较和文本对比是常见需求,比如代码版本变更检测、用户编辑内容的前后差异展示等场景都需要用到相关功能,核心实现思路是基于diff算法完成内容比对。

diff算法的基本原理
diff算法的核心是对比两个序列(文本可以按行拆分为序列)的最小编辑距离,通过插入、删除、保留三种操作,计算出两个序列的差异位置。常见的diff算法实现会先对文本按行拆分,再逐行比对,最终输出差异结果。
php实现文本差异比较的步骤
1. 文本预处理
将需要对比的两段文本按行拆分,转换为数组形式,方便后续逐行比对。如果是对比文件,需要先读取文件内容再拆分。
2. 计算差异矩阵
通过动态规划的方式计算两个文本行数组的最小编辑距离,记录每一步的操作类型。
3. 生成差异结果
根据计算出的操作类型,标记每一行的状态,比如保留、新增、删除,最终输出可视化的差异结果。
php实现diff对比的代码示例
以下是一个简单的php文本差异比较实现,支持两段文本的行级差异对比:
<?php
/**
* php文本差异比较类
*/
class TextDiff
{
// 差异类型常量
const DIFF_EQUAL = 0; // 相同行
const DIFF_ADD = 1; // 新增行
const DIFF_DEL = 2; // 删除行
/**
* 对比两段文本的差异
* @param string $text1 原始文本
* @param string $text2 对比文本
* @return array 差异结果数组
*/
public function compare($text1, $text2)
{
// 按行拆分文本为数组
$lines1 = explode(PHP_EOL, $text1);
$lines2 = explode(PHP_EOL, $text2);
$len1 = count($lines1);
$len2 = count($lines2);
// 初始化动态规划矩阵
$dp = [];
for ($i = 0; $i <= $len1; $i++) {
$dp[$i][0] = $i;
}
for ($j = 0; $j <= $len2; $j++) {
$dp[0][$j] = $j;
}
// 填充动态规划矩阵,计算最小编辑距离
for ($i = 1; $i <= $len1; $i++) {
for ($j = 1; $j <= $len2; $j++) {
if ($lines1[$i - 1] == $lines2[$j - 1]) {
$dp[$i][$j] = $dp[$i - 1][$j - 1];
} else {
$dp[$i][$j] = min($dp[$i - 1][$j], $dp[$i][$j - 1], $dp[$i - 1][$j - 1]) + 1;
}
}
}
// 回溯矩阵生成差异结果
$result = [];
$i = $len1;
$j = $len2;
while ($i > 0 || $j > 0) {
if ($i > 0 && $j > 0 && $lines1[$i - 1] == $lines2[$j - 1]) {
// 当前行相同,加入结果数组
array_unshift($result, [
'type' => self::DIFF_EQUAL,
'line' => $lines1[$i - 1]
]);
$i--;
$j--;
} else {
if ($j > 0 && ($i == 0 || $dp[$i][$j - 1] <= $dp[$i - 1][$j])) {
// 新增行
array_unshift($result, [
'type' => self::DIFF_ADD,
'line' => $lines2[$j - 1]
]);
$j--;
} else {
// 删除行
array_unshift($result, [
'type' => self::DIFF_DEL,
'line' => $lines1[$i - 1]
]);
$i--;
}
}
}
return $result;
}
/**
* 格式化差异结果为可读字符串
* @param array $diffResult compare方法返回的差异结果
* @return string 格式化后的差异字符串
*/
public function formatDiff($diffResult)
{
$output = '';
foreach ($diffResult as $item) {
switch ($item['type']) {
case self::DIFF_EQUAL:
$output .= ' ' . $item['line'] . PHP_EOL;
break;
case self::DIFF_ADD:
$output .= '+' . $item['line'] . PHP_EOL;
break;
case self::DIFF_DEL:
$output .= '-' . $item['line'] . PHP_EOL;
break;
}
}
return $output;
}
}
// 使用示例
$text1 = "hello world
this is first line
php is good";
$text2 = "hello world
this is second line
php is great
new line added";
$diff = new TextDiff();
$result = $diff->compare($text1, $text2);
echo $diff->formatDiff($result);
?>
php对比文件差异的实现
如果需要对比两个文件的差异,只需要在调用对比逻辑前先读取文件内容即可,示例代码如下:
<?php
/**
* 对比两个文件的差异
* @param string $file1 第一个文件路径
* @param string $file2 第二个文件路径
* @return string 差异结果
*/
function compareFiles($file1, $file2)
{
// 检查文件是否存在
if (!file_exists($file1) || !file_exists($file2)) {
return '文件不存在,无法对比';
}
// 读取文件内容
$content1 = file_get_contents($file1);
$content2 = file_get_contents($file2);
$diff = new TextDiff();
$result = $diff->compare($content1, $content2);
return $diff->formatDiff($result);
}
// 调用示例,对比两个本地文件
// echo compareFiles('/tmp/file1.txt', '/tmp/file2.txt');
?>
注意事项
- 如果文本量较大,上述动态规划实现的时间复杂度较高,可以考虑优化算法,比如使用Myers差分算法提升性能。
- 对比文件时需要确保php有对应文件的读取权限,避免出现权限错误。
- 如果需要更完善的差异对比功能,也可以直接使用php成熟的diff扩展,比如xdiff扩展,无需自己实现底层算法。
常见问题解答
diff算法对比的是行级差异还是字符级差异?
上述示例实现的是行级差异对比,如果需要字符级差异,可以将文本按字符拆分而不是按行拆分,调整预处理逻辑即可,不过字符级对比的性能开销会更高。
如何展示更美观的差异结果?
可以在格式化差异结果时,给不同类型的行添加不同的HTML样式,比如删除行用红色背景,新增行用绿色背景,在网页中展示会更清晰。