본문 바로가기

Path와 Files 심화

ch24-1에서 배운 Path와 Files를 바탕으로 정규화, 심볼릭 링크, 파일 속성, 효율적 복사와 원자 교체를 다룹니다.

ch24-1에서는 상대 Path를 만들고 Files.writeString과 readString으로 작은 UTF-8 파일을 다뤘습니다.

여기서는 같은 API를 운영 환경에서 안전하게 사용하기 위한 경로 정규화, 링크, 속성, 복사와 원자 교체를 살펴봅니다.

과거 File은 경로와 작업 메서드를 한 객체에 모았지만 실패 원인이 boolean으로만 보이는 경우가 많았습니다.

새 코드는 예외와 옵션이 명시적인 NIO API를 우선할 수 있습니다.

Path 객체를 만들었다고 실제 파일이 생기지는 않습니다.

상대 경로는 프로세스 작업 디렉터리를 기준으로 해석되며 .과 ..을 포함할 수 있습니다.

사용자 입력 경로를 작업 공간 아래로 제한할 때 문자열 접두사 비교만 하면 우회가 생깁니다.


경로 정규화 순서

workspace.resolve("reports/../../secret.txt")는 표면상 workspace로 시작하지만 계산을 끝내면 상위 디렉터리를 가리킬 수 있습니다.

경로 문자열의 startsWith와 파일 시스템 경로의 Path.startsWith를 혼동해도 우회가 생깁니다.

bad/UncheckedResolvedPath.java
import java.nio.file.Path;

public final class UncheckedResolvedPath {
    public static void main(String[] args) {
        Path workspace = Path.of("safe-workspace").toAbsolutePath();
        String userInput = "reports/../../outside.txt";
        Path unresolved = workspace.resolve(userInput);
        System.out.println("looksInside=" + unresolved.startsWith(workspace));
        System.out.println("normalized=" + unresolved.normalize());
        System.out.println("reallyInside=" + unresolved.normalize().startsWith(workspace));
    }
}

정규화는 ..과 .을 문법적으로 계산하지만 심볼릭 링크가 가리키는 실제 위치까지 확인하지 않습니다.

기존 파일을 열 때는 toRealPath 결과를 허용 루트의 실제 경로와 비교합니다.

새 파일 생성에서는 부모 디렉터리의 실제 경로를 확인하고 링크 교체 경쟁까지 위협 모델에 포함해야 합니다.


작업 공간 내부 경로 제한

경로 검사 함수는 기준을 절대 정규 경로로 만들고 사용자 조각을 resolve한 뒤 다시 정규화합니다.

절대 경로 입력을 허용할지, 빈 경로와 예약 이름을 어떻게 다룰지도 API 계약으로 정합니다.

아래 함수는 문법적 탈출을 막는 기본 단계입니다.

src/WorkspacePath.java
import java.nio.file.Files;
import java.nio.file.Path;

public final class WorkspacePath {
    static Path resolveInside(Path root, String relative) {
        Path base = root.toAbsolutePath().normalize();
        Path candidate = base.resolve(relative).normalize();
        if (!candidate.startsWith(base)) throw new IllegalArgumentException("path escapes root");
        return candidate;
    }

    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("workspace-");
        try {
            Path report = resolveInside(root, "reports/2026.txt");
            Files.createDirectories(report.getParent());
            Files.writeString(report, "ready");
            System.out.println(report.startsWith(root.toAbsolutePath()));
            try {
                resolveInside(root, "../outside.txt");
            } catch (IllegalArgumentException expected) {
                System.out.println("escape rejected");
            }
            Files.deleteIfExists(report);
            Files.deleteIfExists(report.getParent());
        } finally {
            Files.deleteIfExists(root);
        }
    }
}
정규화한 경로 포함 검사는 실제 파일 위치 검사와 다르다

WorkspacePath.resolveInside의 Path.startsWith 검사와 수행하지 않는 파일 시스템 검사를 구분합니다.

경로 제한 · 문법과 실제 위치

정규화한 경로 포함 검사는 실제 파일 위치 검사와 다르다

normalize와 Path.startsWith는 문법적 포함을 확인하지만 심볼릭 링크의 실제 목적지는 조회하지 않습니다.

reports/2026.txt

resolveInside의 판단
정규화한 경로가 base 내부이므로 반환
남는 경계
파일 생성은 함수 바깥에서 수행

../outside.txt

resolveInside의 판단
base 밖이므로 IllegalArgumentException
남는 경계
문자열 접두사 대신 Path 요소를 비교

루트 내부의 절대 경로

resolveInside의 판단
정규화 뒤 base 내부이면 반환 가능
남는 경계
relative 인자 이름만으로 절대 입력을 금지하지 않음

내부 경로가 심볼릭 링크를 경유

resolveInside의 판단
문법상 내부이면 이 검사만으로 거부하지 않음
남는 경계
toRealPath·부모 확인·링크 교체 경쟁은 별도 검토

이 함수는 실제 파일을 조회하지 않습니다. main에는 정상 상대 경로와 ../ 탈출 입력이 있으며, 절대 경로·심볼릭 링크 사례를 실행하는 코드는 없습니다.

실제 업로드 서비스는 서버가 생성한 무작위 파일명을 사용하고 원래 이름은 메타데이터로만 저장하는 편이 안전합니다.

경로 검증 하나에 보안 전체를 의존하지 않습니다.

예시

reports/../ok.txt는 reports 요소를 되돌려 root/ok.txt로 정규화되므로 반환합니다. ../outside.txt는 root 밖이어서 거부합니다. 빈 입력 문자열은 base 자체를 반환하며, base 안의 절대 경로도 이 함수는 거부하지 않습니다. relative라는 인자 이름이 이 계약을 대신하지 않으므로, 새 파일 한 개만 받아야 하는 접점에서는 빈 입력·절대 입력·디렉터리 허용 여부를 추가로 정해야 합니다. 이 계산은 경로 요소의 문법이며 실제 심볼릭 링크 목적지를 확인한 결과가 아닙니다.

파일 권한, 링크 금지, 저장 용량, 확장자와 실제 형식 검사도 함께 적용합니다.


Files 작업의 예외·옵션 해석

Files.createFile은 이미 존재하면 FileAlreadyExistsException을 던집니다.

존재 확인 뒤 생성하는 검사-행동 조합은 경쟁할 수 있으므로 예외 자체를 결과로 처리합니다.

디렉터리 계층은 createDirectories가 이미 존재하는 부모를 허용해 반복 실행에 적합합니다.

src/Utf8FilesWorkflow.java
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;

public final class Utf8FilesWorkflow {
    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("files-flow-");
        Path file = root.resolve("notes/today.txt");
        try {
            Files.createDirectories(file.getParent());
            try {
                Files.createFile(file);
            } catch (FileAlreadyExistsException ignored) {
                System.out.println("reusing existing file");
            }
            Files.write(file, List.of("첫 줄", "second"), StandardCharsets.UTF_8,
                    StandardOpenOption.TRUNCATE_EXISTING);
            System.out.println("size=" + Files.size(file));
            System.out.println(Files.readAllLines(file, StandardCharsets.UTF_8));
        } finally {
            Files.deleteIfExists(file);
            Files.deleteIfExists(file.getParent());
            Files.deleteIfExists(root);
        }
    }
}

기본 write 옵션이 생성과 덮어쓰기를 어떻게 처리하는지 API 문서를 확인하고, 추가가 필요한 로그와 전체 교체 문서를 구분합니다.

민감 파일은 생성 권한을 명시하고 다른 사용자가 읽을 수 없는지 배포 운영체제에서 확인합니다.


File·Path·Files 책임 구분

java.io.File은 오래된 API와의 호환을 위해 경로 표현과 일부 파일 작업을 한 객체에 함께 제공합니다.

Path는 경로 자체를 표현하고, Files는 생성·이동·삭제·속성 조회 같은 작업을 정적 메서드로 수행합니다.

새 코드는 Path와 Files를 기본으로 사용하고, File을 요구하는 기존 라이브러리 접점에서만 path.toFile()과 file.toPath()로 변환합니다.

여러 속성이 필요할 때 Files.size, isDirectory, getLastModifiedTime을 각각 호출하면 조회 사이에 파일 상태가 바뀔 수 있고 시스템 호출도 늘어납니다.

Files.readAttributes(path, BasicFileAttributes.class)는 기본 속성을 한 묶음으로 읽어 파일 종류, 크기, 생성·수정 시각을 같은 조회 결과에서 다루게 합니다.

src/FileToPathBridge.java
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;

public final class FileToPathBridge {
    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("file-bridge-");
        Path path = root.resolve("note.txt");
        try {
            Files.writeString(path, "board", StandardCharsets.UTF_8);

            File legacyFile = path.toFile();
            Path restored = legacyFile.toPath();
            BasicFileAttributes attributes = Files.readAttributes(
                    restored, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);

            System.out.println("name=" + restored.getFileName());
            System.out.println("regular=" + attributes.isRegularFile());
            System.out.println("size=" + attributes.size());
        } finally {
            Files.deleteIfExists(path);
            Files.deleteIfExists(root);
        }
    }
}
name=note.txt
regular=true
size=5

File.exists()로 확인한 뒤 생성하는 두 단계 코드는 검사와 실행 사이에 다른 프로세스가 끼어들 수 있습니다.

Files.createFile, move, delete를 바로 실행하고 구체적인 예외를 결과로 처리합니다.

여러 속성이 하나의 원자적 조회로 읽히는지는 구현에 따라 다릅니다. BasicFileAttributes를 얻어도 이후 파일 상태를 잠그지 않으며, 생성 시각 지원과 정밀도도 파일 시스템마다 다릅니다.

링크를 따라가도 되는지 먼저 정하고, 보안 판단에서는 NOFOLLOW_LINKS와 toRealPath의 의미를 구분합니다.


Files.lines의 자원 범위

readString과 readAllLines는 전체 내용을 메모리에 보관합니다.

로그 통계처럼 각 줄을 한 번만 처리한다면 Files.lines로 전체 줄 목록을 먼저 만들지 않고 읽을 수 있습니다. 한 줄 자체가 매우 길거나 파이프라인이 데이터를 축적하는 경우의 메모리 사용은 별도로 고려합니다.

이 Stream은 열린 파일을 보유하므로 try-with-resources 안에서 최종 연산까지 끝냅니다.

src/StreamingLineStats.java
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public final class StreamingLineStats {
    record Stats(long lines, long nonBlank, long codePoints) {}

    static Stats analyze(Path file) throws Exception {
        try (var lines = Files.lines(file, StandardCharsets.UTF_8)) {
            long[] values = lines.map(line -> new long[] {
                    1,
                    line.isBlank() ? 0 : 1,
                    line.codePointCount(0, line.length())
            }).reduce(new long[3], (left, right) -> new long[] {
                    left[0] + right[0], left[1] + right[1], left[2] + right[2]
            });
            return new Stats(values[0], values[1], values[2]);
        }
    }

    public static void main(String[] args) throws Exception {
        Path file = Files.createTempFile("line-stats-", ".txt");
        try {
            Files.writeString(file, "alpha\n\n한글😀\n", StandardCharsets.UTF_8);
            System.out.println(analyze(file));
        } finally {
            Files.deleteIfExists(file);
        }
    }
}

스트림 파이프라인이 지나치게 복잡해지면 반복문이 더 읽기 쉽고 중간 배열 할당도 줄어듭니다.

집계 항목이 늘거나 행별 오류 위치를 기록해야 한다면 명시적 루프로 바꿔 제어 흐름을 드러내는 편이 낫습니다.

확인 문제

원 analyze 함수에 새 UTF-8 파일 내용 " A😀\r\n \r\n"을 준다면 lines·nonBlank·codePoints는 얼마인가요? 줄 구분자 바이트나 이모지의 두 UTF-16 단위를 codePoints에 합산하는지도 설명하세요.

확인 문제 해설

lines=2, nonBlank=1, codePoints=4입니다. 첫 줄의 공백·A·😀는 코드 포인트 세 개이고 공백뿐인 둘째 줄은 한 개입니다. isBlank는 공백 줄을 nonBlank에서만 빼며 codePoints 계산에서는 그 공백을 포함합니다. Files.lines가 CRLF를 줄 구분자로 제거하므로 구분자 바이트는 셈에 없고, 이모지는 코드 포인트 하나로 셉니다. 이 값은 원 alpha·빈 줄·한글😀 입력을 바꾸지 않고 새 조건을 적용한 결과입니다.


파일 복사 API 선택

단순 복사는 Files.copy(source, target, REPLACE_EXISTING)가 의도를 가장 잘 표현하며 구현이 운영체제 최적화를 활용할 수 있습니다.

스트림 transferTo는 열린 입출력 사이 복사에 적합합니다.

내용 변환이나 해시 진행률이 있으면 직접 청크를 사용합니다.

대상을 직접 덮어쓰면 실패 중 부분 파일이 보일 수 있습니다.

임시 파일에 복사하고 크기 또는 해시를 확인한 뒤 원자 이동을 시도합니다.

파일이 이미 존재할 때 덮을지 실패할지, 속성과 권한도 복사할지, 심볼릭 링크를 따라갈지 옵션을 명시합니다.


Path 선택 기준

작업API핵심 확인
경로 조합resolve와 normalize허용 루트
실제 위치toRealPath심볼릭 링크
부모 생성createDirectories권한
작은 텍스트readString/writeStringCharset과 상한
큰 줄 처리Files.linesStream close
단순 복사Files.copy교체 옵션

연습 문제

원본을 대상 디렉터리의 임시 파일로 복사하고 크기가 같을 때만 최종 이름으로 이동하세요.

이동 전 실패하면 임시 파일 삭제를 시도하세요. 원자 이동 미지원 시 일반 이동을 허용하는 경우에는 이동 실패 뒤 기존 대상의 보존을 보장할 수 없다는 차이도 설명합니다.

같은 파일 시스템에서 원자 이동을 우선 시도합니다.

정답과 해설
exercise/VerifiedAtomicCopySolution.java
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public final class VerifiedAtomicCopySolution {
    static void copy(Path source, Path target) throws Exception {
        Path parent = target.toAbsolutePath().getParent();
        Files.createDirectories(parent);
        Path temp = Files.createTempFile(parent, ".copy-", ".tmp");
        boolean complete = false;
        try {
            Files.copy(source, temp, StandardCopyOption.REPLACE_EXISTING);
            if (Files.size(source) != Files.size(temp)) throw new IllegalStateException("size mismatch");
            try {
                Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE,
                        StandardCopyOption.REPLACE_EXISTING);
            } catch (AtomicMoveNotSupportedException unsupported) {
                Files.move(temp, target, StandardCopyOption.REPLACE_EXISTING);
            }
            complete = true;
        } finally {
            if (!complete) Files.deleteIfExists(temp);
        }
    }

    public static void main(String[] args) throws Exception {
        Path root = Files.createTempDirectory("verified-copy-");
        Path source = root.resolve("source.bin");
        Path target = root.resolve("target.bin");
        try {
            Files.write(source, new byte[] {1, 2, 3});
            copy(source, target);
            System.out.println(Files.size(target));
        } finally {
            Files.deleteIfExists(source);
            Files.deleteIfExists(target);
            Files.deleteIfExists(root);
        }
    }
}

크기 확인과 원자 이동, 대체 이동의 보장은 다르다

임시 복사본 생성, 크기 비교, 이동과 대체 경로, 실패 시 임시 파일 정리의 실행 순서입니다. 크기 일치가 내용 동일성의 증명이 아니라는 조건을 유지하며 공개 시점을 설명할 수 있습니다.

크기 확인과 원자 이동, 대체 이동의 보장은 다르다

VerifiedAtomicCopySolution.copy의 준비·이동·정리 경로를 구분합니다.

파일 복사 · 검증과 이동 경계

크기 확인과 원자 이동, 대체 이동의 보장은 다르다

같은 크기는 같은 내용을 증명하지 않으며, 대체 이동과 정리 실패의 보장도 따로 봐야 합니다.

크기 검사는 이동 전에 내용 검증을 대신하지 못한다

크기 검사는 이동 전에 내용 검증을 대신하지 못한다임시 복사 뒤 크기 불일치는 이동하지 않는다. 크기 일치만으로 내용 손상이나 원본 변경을 검출할 수 없다.아니오예source → 임시 파일복사크기 같음?불일치이동 안 함일치내용 보장없음
크기 검사는 이동 전에 내용 검증을 대신하지 못한다임시 복사 뒤 크기 불일치는 이동하지 않는다. 크기 일치만으로 내용 손상이나 원본 변경을 검출할 수 없다.아니오예source → 임시 파일복사크기 같음?불일치이동 안 함일치내용 보장없음

임시 복사 뒤 크기 불일치는 이동하지 않는다. 크기 일치만으로 내용 손상이나 원본 변경을 검출할 수 없다.

원자 이동과 대체 이동의 다른 보장

원자 이동과 대체 이동의 다른 보장AtomicMoveNotSupportedException인 때만 REPLACE_EXISTING으로 일반 이동한다. 원자 이동과 일반 이동 모두 성공하면 complete=true로 끝나 임시 파일 정리를 생략한다. 다른 원자 이동 실패 또는 일반 이동 실패는 complete=false로 finally에서 남은 임시 파일 삭제를 시도한다. 일반 이동에는 원자성이 없고 실패 시 대상 상태도 확정할 수 없으며 삭제 예외가 원 실패를 가릴 수 있다.성공미지원성공실패ATOMIC_MOVE 요청원자 성공completetrue미지원일반 이동일반 이동 결과?일반 성공completetrue이동 실패completefalse실패 · complete=false남은 temp 삭제 시도
원자 이동과 대체 이동의 다른 보장AtomicMoveNotSupportedException인 때만 REPLACE_EXISTING으로 일반 이동한다. 원자 이동과 일반 이동 모두 성공하면 complete=true로 끝나 임시 파일 정리를 생략한다. 다른 원자 이동 실패 또는 일반 이동 실패는 complete=false로 finally에서 남은 임시 파일 삭제를 시도한다. 일반 이동에는 원자성이 없고 실패 시 대상 상태도 확정할 수 없으며 삭제 예외가 원 실패를 가릴 수 있다.성공미지원성공실패ATOMIC_MOVE 요청원자 성공completetrue미지원일반 이동일반 이동 결과?일반 성공completetrue이동 실패completefalse실패 · complete=false남은 temp 삭제 시도

AtomicMoveNotSupportedException인 때만 REPLACE_EXISTING으로 일반 이동한다. 원자 이동과 일반 이동 모두 성공하면 complete=true로 끝나 임시 파일 정리를 생략한다. 다른 원자 이동 실패 또는 일반 이동 실패는 complete=false로 finally에서 남은 임시 파일 삭제를 시도한다. 일반 이동에는 원자성이 없고 실패 시 대상 상태도 확정할 수 없으며 삭제 예외가 원 실패를 가릴 수 있다.

상태별 상세 해설

임시 파일 복사와 크기 확인

원문의 처리
source와 temp의 size가 같아야 이동
보장하지 않는 것
같은 크기의 내용 손상·복사 중 원본 변경 검출

ATOMIC_MOVE 시도

원문의 처리
원자 이동을 요청
보장하지 않는 것
기존 대상 교체 여부는 구현 의존 · 다른 이동 옵션은 무시됨

원자 이동 미지원

원문의 처리
REPLACE_EXISTING으로 일반 이동
보장하지 않는 것
이동 중 실패하면 기존 대상 보존·완성 상태 보장 없음

complete가 false인 finally

원문의 처리
남은 임시 파일 삭제를 시도
보장하지 않는 것
삭제 실패도 예외이며 원래 실패를 가릴 수 있음

main은 새 대상에 3바이트를 복사하는 입력입니다. 기존 대상 교체·원자 이동 미지원·이동 또는 정리 실패를 재현하는 입력은 없습니다.

이 예의 조건

  • main은 새 대상의 3바이트 복사이며 실패·대체 이동은 미재현 코드 경로입니다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
설명
전체 읽기 정보크기 확인과 원자 이동, 대체 이동의 보장은 다르다 크기 확인과 원자 이동, 대체 이동의 보장은 다르다 VerifiedAtomicCopySolution.copy의 준비·이동·정리 경로를 구분합니다. 파일 복사 · 검증과 이동 경계 크기 확인과 원자 이동, 대체 이동의 보장은 다르다 같은 크기는 같은 내용을 증명하지 않으며, 대체 이동과 정리 실패의 보장도 따로 봐야 합니다. 임시 파일 복사와 크기 확인 원문의 처리 source와 temp의 size가 같아야 이동 보장하지 않는 것 같은 크기의 내용 손상·복사 중 원본 변경 검출 ATOMIC_MOVE 시도 원문의 처리 원자 이동을 요청 보장하지 않는 것 기존 대상 교체 여부는 구현 의존 · 다른 이동 옵션은 무시됨 원자 이동 미지원 원문의 처리 REPLACE_EXISTING으로 일반 이동 보장하지 않는 것 이동 중 실패하면 기존 대상 보존·완성 상태 보장 없음 complete가 false인 finally 원문의 처리 남은 임시 파일 삭제를 시도 보장하지 않는 것 삭제 실패도 예외이며 원래 실패를 가릴 수 있음 main은 새 대상에 3바이트를 복사하는 입력입니다. 기존 대상 교체·원자 이동 미지원·이동 또는 정리 실패를 재현하는 입력은 없습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
임시 복사 검증

source와 temp의 size가 같은지 확인합니다.

  • 검증: 크기 일치
  • 한계: 내용 손상·원본 변경 검출 안 함
  • 크기가 같은 것이 내용 동일성을 증명하지 않습니다.
이동 시도

ATOMIC_MOVE를 요청하고 미지원이면 일반 이동합니다.

  • 원자 이동: 기존 교체 구현 의존
  • 대체 이동: REPLACE_EXISTING
  • 대체 이동 중 실패에서 기존 대상 보존·완성 상태를 보장하지 않습니다.
실패 뒤 정리

complete=false이면 남은 임시 파일 삭제를 시도합니다.

  • 삭제: 실패 가능
  • 예외 우선: 원 실패를 가릴 수 있음
  • 정리도 실패할 수 있어 이동 성공과 따로 확인해야 합니다.

크기 확인과 원자 이동, 대체 이동의 보장은 다르다

임시 복사본 생성, 크기 비교, 이동과 대체 경로, 실패 시 임시 파일 정리의 실행 순서입니다. 크기 일치가 내용 동일성의 증명이 아니라는 조건을 유지하며 공개 시점을 설명할 수 있습니다.

크기 확인과 원자 이동, 대체 이동의 보장은 다르다

VerifiedAtomicCopySolution.copy의 준비·이동·정리 경로를 구분합니다.

파일 복사 · 검증과 이동 경계

크기 확인과 원자 이동, 대체 이동의 보장은 다르다

같은 크기는 같은 내용을 증명하지 않으며, 대체 이동과 정리 실패의 보장도 따로 봐야 합니다.

크기 검사는 이동 전에 내용 검증을 대신하지 못한다

크기 검사는 이동 전에 내용 검증을 대신하지 못한다임시 복사 뒤 크기 불일치는 이동하지 않는다. 크기 일치만으로 내용 손상이나 원본 변경을 검출할 수 없다.아니오예source → 임시 파일복사크기 같음?불일치이동 안 함일치내용 보장없음
크기 검사는 이동 전에 내용 검증을 대신하지 못한다임시 복사 뒤 크기 불일치는 이동하지 않는다. 크기 일치만으로 내용 손상이나 원본 변경을 검출할 수 없다.아니오예source → 임시 파일복사크기 같음?불일치이동 안 함일치내용 보장없음

임시 복사 뒤 크기 불일치는 이동하지 않는다. 크기 일치만으로 내용 손상이나 원본 변경을 검출할 수 없다.

원자 이동과 대체 이동의 다른 보장

원자 이동과 대체 이동의 다른 보장AtomicMoveNotSupportedException인 때만 REPLACE_EXISTING으로 일반 이동한다. 원자 이동과 일반 이동 모두 성공하면 complete=true로 끝나 임시 파일 정리를 생략한다. 다른 원자 이동 실패 또는 일반 이동 실패는 complete=false로 finally에서 남은 임시 파일 삭제를 시도한다. 일반 이동에는 원자성이 없고 실패 시 대상 상태도 확정할 수 없으며 삭제 예외가 원 실패를 가릴 수 있다.성공미지원성공실패ATOMIC_MOVE 요청원자 성공completetrue미지원일반 이동일반 이동 결과?일반 성공completetrue이동 실패completefalse실패 · complete=false남은 temp 삭제 시도
원자 이동과 대체 이동의 다른 보장AtomicMoveNotSupportedException인 때만 REPLACE_EXISTING으로 일반 이동한다. 원자 이동과 일반 이동 모두 성공하면 complete=true로 끝나 임시 파일 정리를 생략한다. 다른 원자 이동 실패 또는 일반 이동 실패는 complete=false로 finally에서 남은 임시 파일 삭제를 시도한다. 일반 이동에는 원자성이 없고 실패 시 대상 상태도 확정할 수 없으며 삭제 예외가 원 실패를 가릴 수 있다.성공미지원성공실패ATOMIC_MOVE 요청원자 성공completetrue미지원일반 이동일반 이동 결과?일반 성공completetrue이동 실패completefalse실패 · complete=false남은 temp 삭제 시도

AtomicMoveNotSupportedException인 때만 REPLACE_EXISTING으로 일반 이동한다. 원자 이동과 일반 이동 모두 성공하면 complete=true로 끝나 임시 파일 정리를 생략한다. 다른 원자 이동 실패 또는 일반 이동 실패는 complete=false로 finally에서 남은 임시 파일 삭제를 시도한다. 일반 이동에는 원자성이 없고 실패 시 대상 상태도 확정할 수 없으며 삭제 예외가 원 실패를 가릴 수 있다.

상태별 상세 해설

임시 파일 복사와 크기 확인

원문의 처리
source와 temp의 size가 같아야 이동
보장하지 않는 것
같은 크기의 내용 손상·복사 중 원본 변경 검출

ATOMIC_MOVE 시도

원문의 처리
원자 이동을 요청
보장하지 않는 것
기존 대상 교체 여부는 구현 의존 · 다른 이동 옵션은 무시됨

원자 이동 미지원

원문의 처리
REPLACE_EXISTING으로 일반 이동
보장하지 않는 것
이동 중 실패하면 기존 대상 보존·완성 상태 보장 없음

complete가 false인 finally

원문의 처리
남은 임시 파일 삭제를 시도
보장하지 않는 것
삭제 실패도 예외이며 원래 실패를 가릴 수 있음

main은 새 대상에 3바이트를 복사하는 입력입니다. 기존 대상 교체·원자 이동 미지원·이동 또는 정리 실패를 재현하는 입력은 없습니다.

이 예의 조건

  • main은 새 대상의 3바이트 복사이며 실패·대체 이동은 미재현 코드 경로입니다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
설명
전체 읽기 정보크기 확인과 원자 이동, 대체 이동의 보장은 다르다 크기 확인과 원자 이동, 대체 이동의 보장은 다르다 VerifiedAtomicCopySolution.copy의 준비·이동·정리 경로를 구분합니다. 파일 복사 · 검증과 이동 경계 크기 확인과 원자 이동, 대체 이동의 보장은 다르다 같은 크기는 같은 내용을 증명하지 않으며, 대체 이동과 정리 실패의 보장도 따로 봐야 합니다. 임시 파일 복사와 크기 확인 원문의 처리 source와 temp의 size가 같아야 이동 보장하지 않는 것 같은 크기의 내용 손상·복사 중 원본 변경 검출 ATOMIC_MOVE 시도 원문의 처리 원자 이동을 요청 보장하지 않는 것 기존 대상 교체 여부는 구현 의존 · 다른 이동 옵션은 무시됨 원자 이동 미지원 원문의 처리 REPLACE_EXISTING으로 일반 이동 보장하지 않는 것 이동 중 실패하면 기존 대상 보존·완성 상태 보장 없음 complete가 false인 finally 원문의 처리 남은 임시 파일 삭제를 시도 보장하지 않는 것 삭제 실패도 예외이며 원래 실패를 가릴 수 있음 main은 새 대상에 3바이트를 복사하는 입력입니다. 기존 대상 교체·원자 이동 미지원·이동 또는 정리 실패를 재현하는 입력은 없습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
임시 복사 검증

source와 temp의 size가 같은지 확인합니다.

  • 검증: 크기 일치
  • 한계: 내용 손상·원본 변경 검출 안 함
  • 크기가 같은 것이 내용 동일성을 증명하지 않습니다.
이동 시도

ATOMIC_MOVE를 요청하고 미지원이면 일반 이동합니다.

  • 원자 이동: 기존 교체 구현 의존
  • 대체 이동: REPLACE_EXISTING
  • 대체 이동 중 실패에서 기존 대상 보존·완성 상태를 보장하지 않습니다.
실패 뒤 정리

complete=false이면 남은 임시 파일 삭제를 시도합니다.

  • 삭제: 실패 가능
  • 예외 우선: 원 실패를 가릴 수 있음
  • 정리도 실패할 수 있어 이동 성공과 따로 확인해야 합니다.

중요한 파일은 해시를 비교하고 권한과 속성 복사 요구도 추가합니다.

예를 들어 원본 [1,2,3]과 손상된 임시 파일 [3,2,1]은 size가 모두 3이어서 크기 비교만 통과합니다. 이 예제의 정상 main이 같은 세 바이트를 복사했다는 사실과 크기 검사가 모든 손상을 검출한다는 주장은 구별하세요. 복사 중 원본 변경까지 막아야 한다면 원본 스냅샷이나 저장 시스템의 일관성 계약도 필요합니다. Files.move의 공식 계약에서 원자 이동은 다른 옵션을 무시하며, 일반 이동 중 IOException이 나면 파일 상태를 확정할 수 없음을 확인할 수 있습니다.

대체 이동을 허용할지 실패시킬지는 애플리케이션의 원자성 요구에 따라 결정합니다.


안전한 파일 작업을 입증하는 조건

Path와 Files를 안전하게 쓰려면 경로를 문자열이 아닌 파일 시스템 값으로 확인하고, 작업별 실패와 옵션을 코드에 드러냅니다.

문법적 경로 검사와 실제 위치 확인을 구분하고, Charset·자원 범위·교체 방식과 실패 처리 정책을 파일 시스템의 보장에 맞춰 선택합니다.