普通文件哈希能找“字节重复”,不能直接认定两张编码或元数据不同的图片完全一致,更不能替你决定删哪份。
一个能核对的字节样本
需要本地 Node.js。下面程序存为独立练习目录的 make-fixture.mjs;它用排他写入,已有同名文件会报错,不覆盖原文件。
js
import fs from 'node:fs';
fs.mkdirSync('samples', {recursive:true});
for (const [name,text] of [['a.txt','alpha\n'],['b.txt','alpha\n'],['c.txt','alpha\r\n']]) {
fs.writeFileSync('samples/'+name,text,{flag:'wx'});
}
运行 node make-fixture.mjs。a 与 b 是 6 字节,c 是 7 字节,换行字节不同。下面存为 audit-bytes.mjs:
js
import fs from 'node:fs/promises';
import path from 'node:path';
import {createReadStream} from 'node:fs';
import {createHash} from 'node:crypto';
const root=path.resolve(process.argv[2]||'samples');
const groups=new Map(), skipped=[];
async function walk(dir) {
for (const e of await fs.readdir(dir,{withFileTypes:true})) {
const p=path.join(dir,e.name);
if(e.isSymbolicLink()){skipped.push({path:p,reason:'symlink'});continue;}
if(e.isDirectory()){await walk(p);continue;}
if(!e.isFile())continue;
const before=await fs.stat(p), h=createHash('sha256');
for await (const chunk of createReadStream(p)) h.update(chunk);
const after=await fs.stat(p);
if(before.size!==after.size||before.mtimeMs!==after.mtimeMs) {
throw new Error('File changed during scan: '+p);
}
const hash=h.digest('hex'), key=before.size+':'+hash;
const row={path:path.relative(root,p),bytes:before.size,sha256:hash};
groups.set(key,[...(groups.get(key)||[]),row]);
}
}
await walk(root);
console.log(JSON.stringify({
root,
duplicates:[...groups.values()].filter(g=>g.length>1),
scannedFiles:[...groups.values()].reduce((n,g)=>n+g.length,0),
skipped
},null,2));
运行 node audit-bytes.mjs samples。预期 scannedFiles=3,只有 a.txt/b.txt 一组;c.txt 不在重复组。程序只读普通文件,符号链接列入 skipped。对同时被写入的目录,先停写或使用快照;前后元数据检查不是并发修改的绝对保证。
哈希相同是极强的字节一致候选,涉及删除可再逐字节比较。Node.js createHash 处理输入字节,不解码图片、不忽略 EXIF、不做文字归一化。
图片不能沿用这个结论
以下是判据示例,并非本程序执行过的图片检测:
| 场景 | 文件哈希 | 还需什么比较 | 结论范围 |
|---|---|---|---|
| 同一 PNG 原样复制,改名 | 相同 | 必要时字节复核 | 字节重复候选 |
| 相同像素重新编码、改变元数据 | 可能不同 | 固定解码器、尺寸、色彩空间、alpha 后逐像素比对 | 约定解码规则下像素一致 |
| 标题位置变了,背景一样 | 通常不同 | 感知相似分数加人工并排检查 | 相似设计版本,不能按重复删除 |
方向、色彩配置、透明度和动画帧也会影响“像素相同”。没有固定比较规则,不要写“图片完全重复”。
让 Codex 做引用对账
text
运行只读字节扫描,生成 duplicates.json。对每组候选查找项目内引用。
输出 path、hash、size、引用文件与位置、用途证据、外部引用未知项。
本轮不删除、不移动。推荐保留哪份时写稳定 URL、调用路径、许可或版本依据。
“视觉相似但哈希不同”是独立任务,不能混入字节重复组。
a 和 b 字节一样,两条公开下载地址仍可能都有人使用。没有搜到本地引用,只能说明本次范围里未发现。后续可看文件移动与引用映射。
下一步
继续阅读与你当前问题相关的教程:


