1. 디렉터리 생성
디렉터리(일명 폴더)는 단순한 파일 컨테이너가 아니라 디스크에서 데이터를 조직하는 기반입니다. 애플리케이션에서는 입력/출력 데이터, 로그, 임시 파일 등을 위해 디렉터리를 수시로 생성합니다. 현대적인 API인 java.nio.file은 이를 편리하고 크로스 플랫폼 방식으로 처리합니다.
클래스 Files, Path, Paths를 사용해 폴더를 생성하고, 삭제하고, 내용을 조회하는 방법을 살펴봅시다.
Files.createDirectory(path)
지정된 경로에 단일 디렉터리를 생성합니다. 상위 디렉터리가 없으면 예외가 발생합니다.
import java.nio.file.*;
public class DirectoryDemo {
public static void main(String[] args) throws Exception {
Path dir = Paths.get("testDir");
if (!Files.exists(dir)) {
Files.createDirectory(dir);
System.out.println("디렉터리가 생성되었습니다: " + dir.toAbsolutePath());
} else {
System.out.println("디렉터리가 이미 존재합니다: " + dir.toAbsolutePath());
}
}
}
이미 존재하는 디렉터리를 생성하려고 시도하면 FileAlreadyExistsException이 발생합니다.
Files.createDirectories(path)
전체 디렉터리 체인을 생성합니다. 중간에 없는 디렉터리가 있으면 자동으로 생성합니다. 체인의 일부가 이미 존재해도 오류가 발생하지 않습니다.
Path nestedDir = Paths.get("parent/child/grandchild");
Files.createDirectories(nestedDir);
System.out.println("디렉터리 체인이 생성되었습니다: " + nestedDir.toAbsolutePath());
마치 집뿐 아니라 현관과 거리까지, 없다면 함께 만들어 달라고 Java에 요청하는 것과 같습니다.
전형적인 예: 프로젝트 구조 생성
예를 들어 보고서를 output/reports/2024 폴더에 저장해야 한다고 합시다. 각 디렉터리를 일일이 확인하지 말고 createDirectories를 사용하세요.
Path reportsPath = Paths.get("output/reports/2024");
Files.createDirectories(reportsPath);
무엇이 이미 존재하고 무엇을 생성해야 하는지는 Java가 알아서 처리합니다.
2. 파일 및 디렉터리 삭제
Files.delete(path)
파일 또는 빈 디렉터리를 삭제합니다. 디렉터리가 비어 있지 않으면 DirectoryNotEmptyException이 발생합니다.
Path dir = Paths.get("testDir");
try {
Files.delete(dir);
System.out.println("디렉터리가 삭제되었습니다: " + dir.toAbsolutePath());
} catch (DirectoryNotEmptyException e) {
System.out.println("삭제할 수 없습니다: 디렉터리가 비어 있지 않습니다!");
}
Files.deleteIfExists(path)
delete처럼 동작하지만, 대상이 없을 때 예외를 던지지 않습니다. 무언가 삭제되면 true를, 대상이 없으면 false를 반환합니다.
Path file = Paths.get("output/reports/2024/report.txt");
if (Files.deleteIfExists(file)) {
System.out.println("파일이 삭제되었습니다.");
} else {
System.out.println("파일을 찾을 수 없어 아무것도 삭제되지 않았습니다.");
}
비어 있지 않은 디렉터리는 어떻게 삭제하나요?
비어 있지 않은 폴더는 그대로 삭제할 수 없습니다. 먼저 그 안의 모든 내용(파일과 하위 디렉터리)을 삭제한 뒤, 마지막에 폴더 자체를 삭제해야 합니다. 이는 재귀적으로 수행하며, 예를 들어 Files.walkFileTree로 구현할 수 있습니다:
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class DeleteDirectoryRecursively {
public static void main(String[] args) throws IOException {
Path dir = Paths.get("parent");
if (Files.exists(dir)) {
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
System.out.println("디렉터리와 그 안의 모든 내용이 삭제되었습니다.");
}
}
}
처음이라면 이 코드가 부담스러울 수 있지만 — 나중에 walkFileTree를 더 자세히 다룹니다.
3. 디렉터리 내용 조회
Files.list(path)
Stream<Path>를 반환합니다 — 경로의 스트림입니다. Stream API를 통해 폴더의 내용을 처리할 수 있습니다. 각 항목은 파일 또는 하위 디렉터리이며(재귀 아님!), 현재 디렉터리의 항목만 포함됩니다.
Path dir = Paths.get("output/reports");
if (Files.exists(dir) && Files.isDirectory(dir)) {
try (var stream = Files.list(dir)) {
stream.forEach(path -> System.out.println(path.getFileName()));
}
}
중요한 사항: 스트림은 반드시 닫아야 하므로 try‑with‑resources를 사용합니다.
예시: 파일 및 하위 디렉터리 이름 출력
Path reports2024 = Paths.get("output/reports/2024");
try (var stream = Files.list(reports2024)) {
stream.forEach(path -> {
String type = Files.isDirectory(path) ? "디렉터리" : "파일";
System.out.println(path.getFileName() + " — " + type);
});
}
컬렉션 형태로 파일 목록 받기
List<Path> files = Files.list(reports2024)
.filter(Files::isRegularFile)
.toList();
System.out.println("폴더의 파일:");
files.forEach(System.out::println);
중요: Files.list(path)는 현재 디렉터리(최상위)의 내용만 반환합니다. 재귀적으로 순회하려면 Files.walk(path)를 사용하세요.
4. 실습: 미니 파일 관리자
유틸리티는 디렉터리 mydata/logs와 파일 log1.txt를 생성하고, 폴더의 내용을 출력한 뒤 모두 삭제합니다.
import java.nio.file.*;
import java.io.IOException;
public class MiniFileManager {
public static void main(String[] args) throws IOException {
Path logsDir = Paths.get("mydata/logs");
Files.createDirectories(logsDir);
Path logFile = logsDir.resolve("log1.txt");
Files.writeString(logFile, "안녕, 로그!");
System.out.println("폴더 " + logsDir + "의 내용:");
try (var stream = Files.list(logsDir)) {
stream.forEach(path -> System.out.println(" - " + path.getFileName()));
}
Files.deleteIfExists(logFile);
Files.deleteIfExists(logsDir);
System.out.println("파일과 폴더가 삭제되었습니다.");
}
}
코드 설명:
- Files.createDirectories는 전체 체인의 생성을 보장합니다.
- resolve — 경로에 파일 이름을 덧붙이는 편리한 방법입니다.
- Files.list에서 얻은 스트림은 try‑with‑resources로 닫습니다 — 특히 Windows에서 중요합니다.
5. 특징과 유의사항
존재 여부 확인
생성/삭제 전에 경로가 존재하는지 확인하는 것이 유용합니다:
if (!Files.exists(path)) {
Files.createDirectory(path);
}
접근 권한
프로그램에 생성/삭제 권한이 없으면 AccessDeniedException이 발생합니다. 예를 들어 일반 사용자로 디스크 루트에 폴더를 만들면 거의 항상 오류가 납니다.
빈 디렉터리와 비어 있지 않은 디렉터리
삭제할 수 있는 것은 빈 디렉터리뿐입니다(Files.delete). 내용을 포함해 삭제하려면 재귀 순회(walkFileTree)를 사용하세요.
크로스 플랫폼
API java.nio.file은 모든 주요 OS에서 올바르게 동작하며 경로 구분자를 고려합니다. 경로를 구성할 때는 문자열을 이어 붙이지 말고 Paths.get과 Path.resolve를 사용하세요.
심볼릭 링크
기본 과제에서는 굳이 고려하지 않아도 되지만, 일반 파일도 디렉터리도 아닌 “이상한” 항목을 만나면 심볼릭 링크(심링크)일 수 있습니다. 고급 강의에서 다시 다루겠습니다.
6. 디렉터리 작업 시 흔한 오류
오류 №1: Files.delete(path)만 호출해 비어 있지 않은 디렉터리를 삭제하려는 경우. Java는 DirectoryNotEmptyException을 던집니다. 먼저 내용을 삭제한 다음 폴더 자체를 삭제하세요.
오류 №2: Files.list()에서 얻은 스트림을 닫지 않음. try‑with‑resources를 사용하지 않으면 스트림이 열린 채로 남아 Windows에서 폴더 삭제 시 오류가 발생할 수 있습니다.
오류 №3: 상위 디렉터리 존재 여부를 확인하지 않고 생성. Files.createDirectory를 parent/child와 같은 경로에 호출할 때 parent가 없으면 NoSuchFileException이 발생합니다. Files.createDirectories를 사용하세요.
오류 №4: 불필요하게 절대 경로 사용. 프로젝트 내부에서는 상대 경로를 선호하세요 — 코드 이식성이 높아집니다.
오류 №5: IOException을 처리하지 않음. 파일 시스템 작업은 언제든 오류로 끝날 수 있습니다. try-catch로 감싸거나 상위로 예외를 던지세요.
오류 №6: 권한이 없는 폴더에서 생성/삭제 시도. 이 경우 AccessDeniedException을 받게 됩니다. 실행 컨텍스트와 접근 권한을 확인하세요.
GO TO FULL VERSION