Approach (high level):- 1st pass: compute cheap, fast fingerprints without loading whole blob — use file size + sample-based fast hash (xxhash64) over a few fixed positions (start, middle, end) to quickly group likely duplicates.- 2nd pass: for groups with matching cheap fingerprints, compute a full cryptographic hash (SHA-256) by streaming the file in chunks (e.g., 4MB) to avoid loading >1MB fully.- 3rd (only if needed): if SHA-256 also matches (extremely rare collision), do a final streaming byte-by-byte comparison between two files to guarantee equality.This minimizes I/O and memory and avoids false positives.Code sketch (python):python
import os, hashlib
import xxhash
CHUNK = 4 * 1024 * 1024 # 4MB
SAMPLES = [0, 65536, -65536] # offsets: start, 64KB, 64KB from end
def sample_hash(path):
size = os.path.getsize(path)
h = xxhash.xxh64()
h.update(str(size).encode())
with open(path, 'rb') as f:
for off in SAMPLES:
if off >= 0:
f.seek(min(off, max(size-1,0)))
else:
f.seek(max(size + off, 0))
chunk = f.read(8192) # small read
h.update(chunk)
return h.intdigest()
def full_sha256(path):
sha = hashlib.sha256()
with open(path, 'rb') as f:
while True:
b = f.read(CHUNK)
if not b: break
sha.update(b)
return sha.hexdigest()
def bytes_equal(a, b):
with open(a,'rb') as fa, open(b,'rb') as fb:
while True:
ba = fa.read(CHUNK)
bb = fb.read(CHUNK)
if not ba and not bb: return True
if ba != bb: return False
def deduplicate(paths):
# 1. cheap fingerprinting
cheap = {}
for p in paths:
k = sample_hash(p)
cheap.setdefault((os.path.getsize(p), k), []).append(p)
uniques = []
for group in cheap.values():
if len(group) == 1:
uniques.append(group[0]); continue
# 2. full hash for group members
by_full = {}
for p in group:
h = full_sha256(p)
by_full.setdefault(h, []).append(p)
# 3. final verification if multiple same sha
for h, members in by_full.items():
rep = members[0]
uniques.append(rep)
for other in members[1:]:
if not bytes_equal(rep, other):
uniques.append(other) # different despite sha (extremely rare)
return uniques
Key design choices & justification:- Sample-based xxhash64 + size: extremely fast, small reads, groups likely duplicates while keeping FP low.- SHA-256 streaming: cryptographically strong, computed incrementally to avoid memory pressure.- Final streaming byte-compare: absolute equality guarantee if hashes collide.- Chunked I/O (4MB): balances syscalls and memory; adjust to environment.Complexity:- I/O: in best case (few collisions), reads only samples + full reads for actual duplicates; worst-case you stream every file once O(total_bytes).- Memory: O(1) per file (streaming buffers); metadata maps sized by number of files.Edge cases:- Very small files (handles via min offsets), permission errors (catch exceptions in production), concurrent file changes (use file locks or snapshot).