Znajdź grupy duplikatów
Jedna funkcja zamienia lista pliki w grupy potwierdzonych duplikatów i nic nie usuwa.
Za pomocą funkcji skrótu z poprzedniej strony, duplikowane wyszukiwanie grupuje pliki według rozmiar, a według digestu SHA-256. Wiele implementacji na tym kończy. SafeSort idzie o krok dalej: zanim rozważy potwierdzoną grupę, porównuje Pliki w nim są wykonane bajt po bajcie.
def files_equal(path_a: Path, path_b: Path, chunk_size: int = DEFAULT_CHUNK_SIZE) -> bool:
„Ostateczne potwierdzenie — zgodny skrót nie gwarantuje.”
with path_a.open("rb") as file_a, path_b.open("rb") as file_b:
while True:
chunk_a = file_a.read(chunk_size)
chunk_b = file_b.read(chunk_size)
if chunk_a != chunk_b:
return False
if not chunk_a:
return True
def find_duplicates(files: list[FileInfo]) -> list[DuplicateGroup]:
by_size = defaultdict(list)
for file in files:
by_size[file.size].append(file)
groups = []
for size, candidates in by_size.items():
if len(candidates) < 2:
continue
by_digest = defaultdict(list)
for candidate in candidates:
digest = sha256_file(candidate.path)
by_digest[digest].append(candidate)
for digest, matched in by_digest.items():
if len(matched) < 2:
continue
exact_groups = []
for candidate in matched:
for exact_group in exact_groups:
if files_equal(exact_group[0].path, candidate.path):
exact_group.append(candidate)
break
else:
exact_groups.append([candidate])
for exact_group in exact_groups:
if len(exact_group) >= 2:
groups.append(DuplicateGroup(size=size, digest=digest, files=tuple(exact_group)))
return groups
Jeden bucket o tej samej wielkości i digestie pozostaje jedynie zestawem kandydatów. Algorytm porównuje kandydata z przedstawicielem każdej już znalezionej grupy: jeśli dodaje się tam, w przeciwnym razie tworzy się nowa klasa. Dlatego nawet sztucznie przywołane Kolizja SHA-256 może wygenerować wiele niezależnych grup o dokładnej zawartości.
Test-punkt kontrolny: dwa identyczne pliki
def test_equal_content_forms_one_group(tmp_path):
a = tmp_path / "a.bin"
b = tmp_path / "b.bin"
a.write_bytes(b"same")
b.write_bytes(b"same")
groups = find_duplicates(scan(tmp_path, Config()))
assert len(groups) == 1
assert {item.path for item in groups[0].files} == {a, b}
sha256_file(). Gdy tylko jeden z elementów nie pasuje, porównanie natychmiast ustaje: nie ma potrzeby dokończać czytania reszty wyraźnie różnych plików.find_duplicates() nie robi wyjątków w tym przypadku — naturalnie należą do tej samej grupy pod względem wielkości (0) i do jednej grupy na podstawie pustego digestu treści. Sprawdzimy to później osobnym testem.Rzeczywiste polecenia duplicates katalogu z trzema egzemplarzami
Pary meczów:
find_duplicates() nie ma ani jednego wywołania, które usuwa pliki, nawet wyłączone lub wykluczone. Automatyczne usuwanie duplikatów jest celowo wykraczające poza zakres pierwszej wersji: decyzja, który z identycznych plików zachować, wymaga kontekstu, którego program nie posiada.