java.nio.file(Path, Files)을 중심으로 파일 읽기/쓰기/복사/이동/삭제/순회, 옵션 상수, 인코딩,java.io스트림 계층, 직렬화, Properties, Zip/GZIP, 임시 파일, WatchService까지 표로 정리했다.Ctrl+F로 메서드명(예:walk,REPLACE_EXISTING,newBufferedReader,createTempFile)을 검색하면 된다. 결과는 JDK 21 / Windows에서 실행한 값이며, 경로 구분자는 Windows 기준(\)이다. Linux/macOS에서는/로 나온다.
| 메서드 | 설명 | 예제 | 결과 |
|---|---|---|---|
Path.of(a, b, ...) |
경로 생성 | Path.of("data", "logs", "app.log") |
data\logs\app.log |
path.resolve(child) |
하위 경로 결합 | Path.of("data").resolve("app.log") |
data\app.log |
path.getFileName() |
파일명 | p.getFileName() |
app.log |
path.getParent() |
상위 디렉터리 | p.getParent() |
data\logs |
Files.exists(p) |
존재 여부 | Files.exists(f) |
true |
Files.readString(p) |
전체를 문자열로 | Files.readString(f) |
"line1\nline2\n한글\n" |
Files.readAllLines(p) |
줄 리스트 | Files.readAllLines(f) |
[line1, line2, 한글] |
Files.lines(p) |
줄 스트림 (close 필수) | try (var s = Files.lines(f)) { s.count(); } |
3 |
Files.writeString(p, s) |
문자열 쓰기 (덮어쓰기) | Files.writeString(f, "hi") |
경로 반환 |
Files.writeString(p, s, APPEND) |
이어 쓰기 | Files.writeString(f, "more\n", StandardOpenOption.APPEND) |
줄 수 4 |
Files.write(p, lines) |
줄 리스트 쓰기 | Files.write(p, List.of("x", "y")) |
파일 생성 |
Files.createDirectories(p) |
중간 경로까지 생성 | Files.createDirectories(base.resolve("d1/d2/d3")) |
d1\d2\d3 |
Files.copy(src, dst, REPLACE_EXISTING) |
복사 | Files.copy(f, dst, StandardCopyOption.REPLACE_EXISTING) |
대상 경로 |
Files.move(src, dst) |
이동/이름 변경 | Files.move(a, b) |
대상 경로 |
Files.delete(p) / deleteIfExists(p) |
삭제 | Files.deleteIfExists(missing) |
false |
Files.size(p) |
바이트 크기 | Files.size(f) |
6 |
Files.list(dir) |
1단계 목록 스트림 | try (var s = Files.list(d)) { ... } |
[d2, e.txt] |
Files.walk(dir) |
재귀 순회 스트림 | try (var s = Files.walk(d)) { ... } |
모든 하위 |
Files.newBufferedReader(p) |
큰 파일 줄 단위 읽기 | try (var r = Files.newBufferedReader(p)) { r.readLine(); } |
"a" |
Files.newBufferedWriter(p) |
큰 파일 쓰기 | try (var w = Files.newBufferedWriter(p)) { w.write("a"); } |
파일 생성 |
| 메서드/시그니처 | 설명 | 한 줄 예제 (p = data\logs\app.log) |
결과 |
|---|---|---|---|
Path.of(String, String...) |
경로 생성 (JDK 11+) | Path.of("data", "logs", "app.log") |
data\logs\app.log |
Paths.get(String) |
구식 생성 (동일) | Paths.get("a/b.txt") |
a\b.txt |
getFileName() |
마지막 요소 | p.getFileName() |
app.log |
getParent() |
상위 (없으면 null) | p.getParent() |
data\logs |
getRoot() |
루트 (상대 경로면 null) | p.getRoot() + " " + Path.of("C:/x").getRoot() |
null C:\ |
getNameCount() |
요소 수 | p.getNameCount() |
3 |
getName(i) |
i번째 요소 | p.getName(0) |
data |
subpath(a, b) |
요소 구간 | p.subpath(1, 3) |
logs\app.log |
resolve(other) |
하위 결합 (other가 절대면 other) | Path.of("data").resolve("app.log") |
data\app.log |
resolve(절대경로) |
절대 경로는 그대로 | Path.of("data").resolve("C:/abs/x.txt") |
C:\abs\x.txt |
resolveSibling(name) |
형제 경로 | p.resolveSibling("app.bak") |
data\logs\app.bak |
relativize(other) |
상대 경로 계산 | Path.of("a/b").relativize(Path.of("a/b/c/d.txt")) |
c\d.txt |
relativize 상위로 |
.. 포함 |
Path.of("a/b/c").relativize(Path.of("a/x")) |
..\..\x |
normalize() |
./.. 정리 |
Path.of("a/./b/../c").normalize() |
a\c |
isAbsolute() |
절대 경로 여부 | p.isAbsolute() + " " + Path.of("C:/x").isAbsolute() |
false true |
toAbsolutePath() |
작업 디렉터리 기준 절대화 | Path.of("x").toAbsolutePath() |
C:\...\x |
toRealPath() |
심볼릭 링크 해석 (존재해야 함) | p.toRealPath() |
실제 경로 (IOException 가능) |
startsWith(s) / endsWith(s) |
요소 단위 비교 | p.startsWith("dat") + " " + p.endsWith(".log") |
false false |
startsWith("data") / endsWith("app.log") |
요소 일치 | 왼쪽 | true true |
toString() |
문자열 (OS 구분자) | p.toString() |
data\logs\app.log |
toUri() |
URI | Path.of("C:/a b.txt").toUri() |
file:///C:/a%20b.txt |
toFile() / File.toPath() |
java.io 상호 변환 | new File("x.txt").toPath() |
x.txt |
iterator() / spliterator() |
요소 순회 | StreamSupport.stream(p.spliterator(), false).toList() |
[data, logs, app.log] |
equals / compareTo |
경로 비교 (문자열 기반) | Path.of("a/b").equals(Path.of("a", "b")) |
true |
File.separator / File.pathSeparator |
구분자 상수 | "[" + File.separator + "] [" + File.pathSeparator + "]" |
[\] [;] |
FileSystems.getDefault().getSeparator() |
동일 | 왼쪽 | \ |
| 확장자 추출 (표준 메서드 없음) | lastIndexOf('.') |
n.substring(n.lastIndexOf('.') + 1) |
log |
Path는 파일이 실제로 존재하지 않아도 만들 수 있다. 존재 검사는 Files.exists.dir + "/" + name) 대신 resolve를 쓰면 OS 구분자 문제가 사라진다.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
readString(p) |
전체 문자열 (UTF-8, 작은 파일) | Files.readString(f) |
"line1\nline2\n한글\n" |
readString(p, charset) |
인코딩 지정 | Files.readString(p, Charset.forName("EUC-KR")) |
"한글" |
readAllLines(p) |
List<String> (메모리 전체 적재) |
Files.readAllLines(f) |
[line1, line2, 한글] |
readAllBytes(p) |
byte[] |
Files.readAllBytes(p).length |
6 |
lines(p) |
Stream<String> (lazy, try-with-resources) |
아래 코드 | [line1, line2] |
newBufferedReader(p) |
큰 파일 줄 단위 | try (var r = Files.newBufferedReader(p)) { r.readLine(); } |
"a", 끝이면 null |
newBufferedReader(p).lines() |
Reader → 스트림 | r.lines().toList() |
[a, b] |
newInputStream(p) |
바이너리 읽기 | try (var in = Files.newInputStream(p)) { in.read(); } |
첫 바이트 1 |
writeString(p, s) |
덮어쓰기 (CREATE+TRUNCATE+WRITE) | Files.writeString(f, "line1\nline2\n") |
경로 반환 |
writeString(p, s, charset, options...) |
인코딩+옵션 | Files.writeString(p, "한글", Charset.forName("EUC-KR")) |
크기 4 |
write(p, bytes) |
바이트 쓰기 | Files.write(p, new byte[]{1, 2, 3}) |
경로 반환 |
write(p, Iterable<String>) |
줄 리스트 (줄마다 개행) | Files.write(p, List.of("x", "y")) |
파일 크기 6 (\r\n 포함) |
newBufferedWriter(p, charset) |
큰 파일 쓰기 | try (var w = Files.newBufferedWriter(p, UTF_8)) { w.write("a"); } |
파일 생성 |
newOutputStream(p) |
바이너리 쓰기 | try (var out = Files.newOutputStream(p)) { out.write(65); } |
파일 내용 "A" |
new PrintWriter(Files.newBufferedWriter(p)) |
printf/println | pw.printf("%d-%s%n", 1, "a"); pw.println("b") |
[1-a, b] |
copy(InputStream, p) |
스트림 → 파일 | Files.copy(new ByteArrayInputStream("from stream".getBytes()), p) |
11 (바이트 수) |
copy(p, OutputStream) |
파일 → 스트림 | Files.copy(p, byteArrayOut) |
내용 복사 |
Scanner(path) |
토큰 파싱 | try (Scanner sc = new Scanner(f)) { sc.nextLine(); } |
첫 줄 |
RandomAccessFile |
임의 위치 접근 | raf.seek(2); (char) raf.read() |
3번째 문자 |
lines(p) 예제는 아래 코드와 같습니다.
try (Stream<String> s = Files.lines(f)) {
return s.filter(l -> l.startsWith("line")).toList();
}readString/readAllLines는 파일 전체를 메모리에 올린다. 수백 MB 이상은 lines()나 newBufferedReader.Files.lines, Files.list, Files.walk는 파일 핸들을 잡고 있는 스트림이다. 반드시 try-with-resources.| 상수/메서드 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
StandardOpenOption.CREATE |
없으면 생성 (있으면 열기) | writeString(p, "n", CREATE, APPEND) (새 파일) |
생성됨 |
StandardOpenOption.CREATE_NEW |
없을 때만 생성, 있으면 예외 | writeString(f, "x", CREATE_NEW) (존재) |
FileAlreadyExistsException |
StandardOpenOption.APPEND |
끝에 추가 | writeString(f, "more\n", APPEND) |
줄 수 4 |
StandardOpenOption.TRUNCATE_EXISTING |
열 때 비우기 | writeString(f, "reset\n", TRUNCATE_EXISTING) |
내용 reset |
StandardOpenOption.WRITE |
쓰기 (파일 있어야 함) | writeString(missing, "x", WRITE) |
NoSuchFileException |
StandardOpenOption.READ |
읽기 | Files.newInputStream(p, READ) |
읽기 스트림 |
StandardOpenOption.DELETE_ON_CLOSE |
닫을 때 삭제 | 임시 작업 파일 | 자동 삭제 |
StandardOpenOption.SYNC / DSYNC |
동기 쓰기 | 내구성 필요 시 | 느림 |
기본 옵션 (writeString(p, s)) |
CREATE, TRUNCATE_EXISTING, WRITE |
항상 덮어쓰기 | |
StandardCopyOption.REPLACE_EXISTING |
대상 있으면 덮어쓰기 | Files.copy(f, dst, REPLACE_EXISTING) |
성공 |
copy 옵션 없이 대상 존재 |
예외 | Files.copy(f, existing) |
FileAlreadyExistsException |
StandardCopyOption.COPY_ATTRIBUTES |
수정시각 등 속성 복사 | Files.copy(f, dst, COPY_ATTRIBUTES) |
성공 |
StandardCopyOption.ATOMIC_MOVE |
원자적 이동 (같은 파일시스템) | Files.move(src, dst, ATOMIC_MOVE) |
성공 |
LinkOption.NOFOLLOW_LINKS |
심볼릭 링크 따라가지 않음 | Files.exists(p, NOFOLLOW_LINKS) |
링크 자체 검사 |
StandardCharsets.UTF_8 |
상수 (권장) | Files.readString(p, StandardCharsets.UTF_8) |
|
Charset.forName("EUC-KR") / "MS949" |
한글 레거시 | Files.writeString(p, "한글", Charset.forName("EUC-KR")) |
크기 4 |
| 잘못된 인코딩으로 readString | 예외 (JDK 11+ Files.readString) | Files.readString(eucFile) (UTF-8 기본) |
MalformedInputException |
| 잘못된 인코딩으로 new String | 깨진 문자 (예외 없음) | new String(Files.readAllBytes(eucFile), UTF_8) |
"�ѱ�" |
Charset.defaultCharset() |
기본 인코딩 (JDK 18+ UTF-8) | Charset.defaultCharset() |
UTF-8 |
System.getProperty("file.encoding") |
동일 | 왼쪽 | UTF-8 |
-Dfile.encoding=UTF-8 / -Dstdout.encoding=UTF-8 |
JVM 옵션 | 콘솔 한글 깨짐 대응 |
Files.readString은 잘못된 바이트를 만나면 예외를 던지고, new String(bytes, cs)는 조용히 �로 바꾼다. 인코딩 오류를 잡으려면 전자가 낫다.MS949는 EUC-KR의 확장이라 한글 8,822자를 모두 표현한다.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
exists(p) / notExists(p) |
존재 (둘 다 false면 판단 불가) | Files.exists(f) + " " + Files.notExists(missing) |
true true |
isRegularFile(p) / isDirectory(p) |
종류 | Files.isRegularFile(f) + " " + Files.isDirectory(base) |
true true |
isReadable/isWritable/isExecutable/isHidden |
권한·속성 | Files.isReadable(f) + "/" + Files.isHidden(f) |
true/false |
size(p) |
바이트 | Files.size(f) |
6 |
getLastModifiedTime(p) |
FileTime |
Files.getLastModifiedTime(f).toInstant() |
Instant |
setLastModifiedTime(p, FileTime) |
수정 시각 변경 | Files.setLastModifiedTime(f, FileTime.fromMillis(0)) → 조회 |
1970-01-01T00:00:00Z |
readAttributes(p, BasicFileAttributes.class) |
속성 묶음 | 아래 코드 | 6 dir=false created=... |
getAttribute(p, "size") |
문자열 키로 속성 | Files.getAttribute(f, "size") |
6 |
probeContentType(p) |
MIME 추정 | Files.probeContentType(f) |
text/plain |
isSameFile(a, b) |
같은 파일인지 | Files.isSameFile(f, base.resolve("./a.txt")) |
true |
getOwner(p) |
소유자 | Files.getOwner(f) |
UserPrincipal |
createDirectories(p) |
중간 경로 포함 생성 (있어도 OK) | Files.createDirectories(base.resolve("d1/d2/d3")) |
d1\d2\d3 |
createDirectory(p) |
1단계만, 있으면 예외 | Files.createDirectory(existing) |
FileAlreadyExistsException |
createFile(p) |
빈 파일 생성, 있으면 예외 | Files.createFile(existing) |
FileAlreadyExistsException |
copy(src, dst) |
복사 (대상 있으면 예외) | Files.copy(f, base.resolve("a_copy.txt")) |
a_copy.txt |
copy(src, dst, REPLACE_EXISTING) |
덮어쓰기 | 왼쪽 | 성공 |
copy 디렉터리 |
빈 디렉터리만 복사 (내용은 walk로) | 스니펫 참고 | |
move(src, dst) |
이동/이름 변경 | Files.move(a, base.resolve("renamed.txt")) |
renamed.txt |
move(src, dst, REPLACE_EXISTING) |
덮어쓰기 이동 | Files.move(a, existing, REPLACE_EXISTING) |
성공 |
move(src, dst, ATOMIC_MOVE) |
원자적 (안전한 덮어쓰기용) | Files.move(tmp, target, ATOMIC_MOVE) |
성공 |
delete(p) |
삭제, 없으면 예외 | Files.delete(missing) |
NoSuchFileException |
deleteIfExists(p) |
삭제 여부 반환 | Files.deleteIfExists(missing) + "/" + Files.deleteIfExists(f) |
false/true |
delete 비어있지 않은 디렉터리 |
예외 | Files.delete(nonEmptyDir) |
DirectoryNotEmptyException |
| 부모 없는 경로에 쓰기 | 예외 | Files.writeString(base.resolve("nodir/f.txt"), "x") |
NoSuchFileException |
File API (구식) |
exists/length/getName/isFile/getParentFile |
f.toFile().length() |
6 |
File.listFiles() |
구식 목록 (null 가능) | dir.toFile().listFiles() |
File[] |
readAttributes 예제는 아래 코드와 같습니다.
var attrs = Files.readAttributes(p, BasicFileAttributes.class);
attrs.size() + " dir=" + attrs.isDirectory() + " created=" + attrs.creationTime();Files.createDirectories(p.getParent())를 호출하는 습관을 들인다.Files.walk(...).sorted(reverseOrder()) 순서로 삭제한다(스니펫).| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
list(dir) |
1단계만, lazy 스트림 | 아래 코드 1 | [d2, e.txt] |
walk(dir) |
재귀 (자기 자신 포함, 깊이 우선) | try (var s = Files.walk(d1)) { s.map(base::relativize).toList(); } |
아래 결과 |
walk(dir, maxDepth) |
깊이 제한 | Files.walk(d1, 1).count() |
3 (자신 + 1단계 2개) |
walk + 필터 |
확장자 검색 | s.filter(x -> x.toString().endsWith(".java")) |
[x.java, y.java] |
find(dir, depth, (path, attrs) -> bool) |
속성 조건 검색 | Files.find(d1, 10, (x, a) -> a.isRegularFile() && a.size() > 5) |
[x.java, y.java] |
newDirectoryStream(dir, glob) |
glob 필터 (for-each) | 아래 코드 2 | [x.java, y.java] |
FileSystems.getDefault().getPathMatcher("glob:...") |
경로 매처 | pm.matches(Path.of("a/b/c.java")) (glob:**/*.{java,txt}) |
true |
walkFileTree(dir, FileVisitor) |
콜백 방식 (preVisit/visitFile/postVisit) | SimpleFileVisitor 오버라이드 |
[e.txt, x.java, y.java, z.txt] |
FileVisitResult.CONTINUE/SKIP_SUBTREE/TERMINATE |
순회 제어 | return SKIP_SUBTREE (node_modules 등) |
하위 건너뜀 |
File.listFiles() |
구식, 배열 | Arrays.stream(dir.toFile().listFiles()).map(File::getName) |
[d2, e.txt] |
아래 코드 1: list(dir) 예제. 아래 코드 2: newDirectoryStream 예제.
walk(dir) 예제의 아래 결과는 d1 아래 모든 경로입니다.
// 아래 코드 1
try (var s = Files.list(d1)) {
s.map(x -> x.getFileName().toString()).sorted().toList();
}
// 아래 코드 2
try (var ds = Files.newDirectoryStream(d2, "*.java")) {
for (Path x : ds) { ... }
}아래 결과: [d1, d1/d2, d1/d2/d3, d1/d2/d3/z.txt, d1/d2/x.java, d1/d2/y.java, d1/e.txt]walk는 심볼릭 링크를 기본으로 따라가지 않는다. 필요하면 FileVisitOption.FOLLOW_LINKS.walk가 UncheckedIOException을 던진다. 견고하게 하려면 walkFileTree의 visitFileFailed를 오버라이드.| 분류 | 클래스 | 용도 | 한 줄 예제 | 결과 |
|---|---|---|---|---|
| 바이트 입력 | FileInputStream |
파일 바이트 | new FileInputStream(file) |
스트림 |
| 바이트 출력 | FileOutputStream |
파일 바이트 (2번째 인자 true = append) | new FileOutputStream(file, true) |
스트림 |
| 버퍼 | BufferedInputStream / BufferedOutputStream |
8KB 버퍼로 감싸 성능 | new BufferedInputStream(new FileInputStream(f)) |
스트림 |
| 기본형 | DataInputStream / DataOutputStream |
int/double/UTF 쓰고 읽기 | out.writeInt(42); out.writeUTF("hi"); out.writeDouble(1.5) → 읽기 |
42/hi/1.5 (16바이트) |
| 객체 | ObjectInputStream / ObjectOutputStream |
직렬화 (8절) | oos.writeObject(obj) |
바이트 |
| 메모리 | ByteArrayInputStream / ByteArrayOutputStream |
바이트 배열 ↔ 스트림 | 아래 코드 1 | "abc" |
| 문자 입력 | FileReader |
파일 문자 (charset 지정 생성자 JDK 11+) | new FileReader(f, UTF_8) |
Reader |
| 문자 출력 | FileWriter |
파일 문자 | new FileWriter(f, UTF_8, true) (append) |
"appended" |
| 브리지 | InputStreamReader / OutputStreamWriter |
바이트 ↔ 문자 (charset 명시) | 아래 코드 2 | Reader |
| 버퍼 문자 | BufferedReader / BufferedWriter |
readLine(), newLine() |
r.readLine() |
한 줄 |
| 출력 편의 | PrintWriter / PrintStream |
printf/println (예외 삼킴 주의) |
new PrintWriter(sw, true).printf("%s=%d", "a", 1) |
"a=1" |
| 메모리 문자 | StringReader / StringWriter |
문자열 ↔ Reader/Writer | new BufferedReader(new StringReader("l1\nl2")).lines().toList() |
[l1, l2] |
| 토큰 | Scanner |
공백 구분 파싱 | 아래 코드 3 | 30 |
| 임의 접근 | RandomAccessFile |
seek/read/write | raf.seek(2); (char) raf.read() |
's' |
| 공통 | InputStream.readAllBytes() |
전체 읽기 (JDK 9+) | in.readAllBytes() |
byte[] |
| 공통 | InputStream.readNBytes(n) |
n바이트 | new ByteArrayInputStream("abcdef".getBytes()).readNBytes(3) |
"abc" |
| 공통 | InputStream.transferTo(out) |
복사 (JDK 9+) | in.transferTo(byteArrayOut) |
"xyz" |
| 표준 | System.in / System.out / System.err |
콘솔 | new BufferedReader(new InputStreamReader(System.in)).readLine() |
입력 한 줄 |
아래 코드 1~3은 각각 메모리 스트림, 브리지, Scanner 예제입니다.
// 아래 코드 1
new String(new ByteArrayInputStream("abc".getBytes()).readAllBytes());
// 아래 코드 2
new BufferedReader(new InputStreamReader(new FileInputStream(f), UTF_8));
// 아래 코드 3
Scanner sc = new Scanner("10 20 hello");
sc.nextInt() + sc.nextInt();try-with-resources 규칙:
| 상황 | 동작 | 예제 | 결과 |
|---|---|---|---|
| 여러 리소스 | 선언 역순으로 close | try (A a = ...; B b = ...) { } |
[body, close B, close A] |
| 본문 예외 + close 예외 | close 예외는 getSuppressed()에 |
try (r) { throw new IllegalStateException("body fail"); } |
body fail suppressed=close fail |
| effectively final 변수 | JDK 9+ try (existingVar) |
BufferedReader r = ...; try (r) { } |
닫힘 |
AutoCloseable 람다 |
테스트용 | try (AutoCloseable a = () -> log("close")) { } |
호출됨 |
InputStreamReader(in, charset)으로 감싼다. FileReader의 인자 없는 생성자는 플랫폼 기본 인코딩을 쓴다.PrintWriter는 IOException을 삼킨다. checkError()로 확인하거나 BufferedWriter를 쓴다.| 항목 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
implements Serializable |
마커 인터페이스 | record User(String name, int age) implements Serializable {} |
직렬화 가능 |
ObjectOutputStream.writeObject(o) |
쓰기 | 아래 코드 | 파일 |
ObjectInputStream.readObject() |
읽기 (캐스팅, ClassNotFoundException) | (User) ois.readObject() |
User[name=Kim, age=30] |
transient |
직렬화 제외 (읽으면 기본값) | transient String secret = "pw" → 역직렬화 |
Session[id=s1, secret=null] |
serialVersionUID |
클래스 버전 (명시 권장) | private static final long serialVersionUID = 1L; |
불일치 시 InvalidClassException |
| 미구현 클래스 | 예외 | oos.writeObject(new Object()) |
NotSerializableException |
| record 직렬화 | 생성자 통해 복원 (JDK 16+) | 위 User 예제 | 정상 |
| 포함 객체 | 모든 필드가 Serializable이어야 | List<User> 필드 |
ArrayList은 OK |
| 보안 | 신뢰할 수 없는 바이트 역직렬화 금지 | ObjectInputFilter 설정 |
필터링 |
writeObject 예제는 아래 코드와 같습니다.
try (var oos = new ObjectOutputStream(Files.newOutputStream(p))) {
oos.writeObject(new User("Kim", 30));
}| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
new Properties() |
생성 | Properties props = new Properties() |
빈 |
load(Reader) |
읽기 (Reader로 UTF-8) | try (var r = Files.newBufferedReader(p, UTF_8)) { props.load(r); } |
로드 |
load(InputStream) |
ISO-8859-1로 읽음 (한글 깨짐) | props.load(Files.newInputStream(p)) → getProperty("app.title") |
"íê¸ ì 목" |
getProperty(key) |
값 (없으면 null) | props.getProperty("app.name") |
"Demo" |
getProperty(key, default) |
기본값 | props.getProperty("app.missing", "default") |
"default" |
| 숫자 변환 | 직접 파싱 | Integer.parseInt(props.getProperty("app.port")) |
8080 |
| 목록 변환 | split | Arrays.asList(props.getProperty("app.list").split(",")) |
[a, b, c] |
setProperty(k, v) |
설정 | props.setProperty("app.new", "v") |
이전 값 |
stringPropertyNames() |
키 집합 | new TreeSet<>(props.stringPropertyNames()) |
[app.list, app.name, app.port, app.title] |
store(Writer, comment) |
저장 (날짜 주석 자동 추가) | 아래 코드 | app.new=v 줄 포함 |
System.getProperty(key) |
JVM 시스템 프로퍼티 | System.getProperty("java.version") |
21.0.8 |
System.getProperties() |
전체 | System.getProperties().size() |
수십 개 |
| 파일 형식 | key=value, # 주석, \ 줄 연속 |
app.name=Demo |
store 예제는 아래 코드와 같습니다.
try (var w = Files.newBufferedWriter(out)) {
props.store(w, "saved");
}# comment
app.name=Demo
app.port=8080
app.title=한글 제목
app.list=a,b,cload(InputStream)은 ISO-8859-1 고정이다. 한글은 load(Reader)를 쓰거나 \uXXXX로 이스케이프한다(JDK 9+ .properties 리소스 번들은 UTF-8 기본).getClass().getResourceAsStream("/app.properties")로 읽고, 없으면 null이므로 검사한다.| 메서드/시그니처 | 설명 | 한 줄 예제 | 결과 |
|---|---|---|---|
ZipOutputStream(out) |
zip 쓰기 | 아래 코드 1 | 엔트리 추가 |
| 하위 경로 엔트리 | /로 구분 |
new ZipEntry("dir/n.txt") |
폴더 구조 |
ZipInputStream(in) |
zip 순차 읽기 | while ((e = zis.getNextEntry()) != null) { zis.readAllBytes(); } |
[hello.txt:hello zip, dir/n.txt:nested] |
ZipFile(file) |
zip 임의 접근 | zf.stream().map(ZipEntry::getName).toList() + " size=" + zf.size() |
[hello.txt, dir/n.txt] size=2 |
ZipFile.getInputStream(entry) |
특정 엔트리 읽기 | zf.getInputStream(zf.getEntry("hello.txt")) |
스트림 |
ZipEntry.isDirectory() |
디렉터리 엔트리 | e.isDirectory() |
boolean |
FileSystems.newFileSystem(zipPath) |
zip을 파일시스템으로 (JDK 13+) | 아래 코드 2 | "hello zip" |
GZIPOutputStream(out) |
gzip 쓰기 | out.write("gzip content ".repeat(100).getBytes()) |
1300자 → 44바이트 |
GZIPInputStream(in) |
gzip 읽기 | new String(in.readAllBytes()).length() |
1300 |
| gzip 텍스트 줄 읽기 | Reader로 감싸기 | 아래 코드 3 | 첫 줄 |
Files.createTempFile(prefix, suffix) |
임시 파일 (java.io.tmpdir) | Files.createTempFile("pre_", ".tmp") |
pre_123.tmp 존재 |
Files.createTempFile(dir, prefix, suffix) |
위치 지정 | Files.createTempFile(base, "x", ".log") |
base 아래 |
Files.createTempDirectory(prefix) |
임시 디렉터리 | Files.createTempDirectory("tmpdir") |
tmpdir123 |
System.getProperty("java.io.tmpdir") |
임시 디렉터리 위치 | 왼쪽 | C:\Users\...\Temp |
file.deleteOnExit() |
JVM 종료 시 삭제 (java.io) | tmp.toFile().deleteOnExit() |
등록 |
StandardOpenOption.DELETE_ON_CLOSE |
닫을 때 삭제 | Files.newOutputStream(tmp, DELETE_ON_CLOSE) |
자동 삭제 |
아래 코드 1~3은 각각 zip 쓰기, zip 파일시스템, gzip 줄 읽기 예제입니다.
// 아래 코드 1
zos.putNextEntry(new ZipEntry("hello.txt"));
zos.write(bytes);
zos.closeEntry();
// 아래 코드 2
try (FileSystem zfs = FileSystems.newFileSystem(zip)) {
Files.readString(zfs.getPath("hello.txt"));
}
// 아래 코드 3
new BufferedReader(new InputStreamReader(new GZIPInputStream(in), UTF_8))
.readLine();finally에서 deleteIfExists하거나 DELETE_ON_CLOSE를 쓴다.../가 들어간 파일을 그대로 풀면 경로 탈출(Zip Slip)이 된다. dst.resolve(name).normalize().startsWith(dst)를 확인한다.설정 파일이나 결과 파일을 직접 덮어쓰면 쓰는 도중 프로세스가 죽었을 때 파일이 반쯤 깨진다. 같은 디렉터리에 임시 파일로 완전히 쓴 뒤 ATOMIC_MOVE로 교체하면 언제나 "이전 버전 아니면 새 버전"만 존재한다.
static void safeOverwrite(Path target, String content) throws IOException {
Path tmp = target.resolveSibling(target.getFileName() + ".tmp");
Files.writeString(tmp, content, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
}
Files.writeString(target, "{\"v\":1}");
safeOverwrite(target, "{\"v\":2}");
Files.readString(target); // 출력: {"v":2}
Files.exists(target.resolveSibling("config.json.tmp")); // 출력: falseFiles.walk로 재귀 순회하고 일반 파일만 걸러 Files.size를 합산한다. 람다 안에서는 checked 예외를 던질 수 없으므로 내부에서 잡는다. 확장자별 그룹핑은 groupingBy로.
static long dirSize(Path dir) throws IOException {
try (Stream<Path> s = Files.walk(dir)) {
return s.filter(Files::isRegularFile)
.mapToLong(p -> { try { return Files.size(p); } catch (IOException e) { return 0L; } })
.sum();
}
}
static Map<String, List<String>> byExtension(Path dir) throws IOException {
try (Stream<Path> s = Files.walk(dir)) {
return s.filter(Files::isRegularFile).collect(Collectors.groupingBy(p -> {
String n = p.getFileName().toString();
int i = n.lastIndexOf('.');
return i < 0 ? "(none)" : n.substring(i + 1).toLowerCase();
}, TreeMap::new, Collectors.mapping(p -> p.getFileName().toString(), Collectors.toList())));
}
}
dirSize(d1); // 출력: 20
byExtension(d1); // 출력: {java=[x.java, y.java], txt=[z.txt, e.txt]}10만 줄(1.1MB) 파일. Files.lines().count()는 메모리에 다 올리지 않고 세며, BufferedReader.readLine() 루프도 같다. 특정 패턴 검색이나 앞 N줄 미리보기도 스트림으로 처리하면 필요한 만큼만 읽는다.
static long countLinesStream(Path p) throws IOException {
try (Stream<String> s = Files.lines(p)) { return s.count(); }
}
static long countLinesReader(Path p) throws IOException {
try (BufferedReader r = Files.newBufferedReader(p)) {
long n = 0;
while (r.readLine() != null) n++;
return n;
}
}
countLinesStream(big); // 출력: 100000
countLinesReader(big); // 출력: 100000
Files.size(big); // 출력: 1188890
try (Stream<String> s = Files.lines(big)) { s.filter(l -> l.endsWith("99999")).toList(); }
// 출력: [line 99999]
try (Stream<String> s = Files.lines(big)) { s.limit(3).toList(); }
// 출력: [line 0, line 1, line 2]Files.copy는 디렉터리 내용을 복사하지 않는다. walk로 모든 경로를 돌며 디렉터리는 createDirectories, 파일은 copy. 삭제는 자식이 먼저 지워져야 하므로 역순 정렬(reverseOrder)로 돈다.
static void copyDir(Path src, Path dst) throws IOException {
try (Stream<Path> s = Files.walk(src)) {
for (Path p : s.toList()) {
Path t = dst.resolve(src.relativize(p));
if (Files.isDirectory(p)) Files.createDirectories(t);
else Files.copy(p, t, StandardCopyOption.REPLACE_EXISTING);
}
}
}
static void deleteRecursively(Path root) throws IOException {
if (!Files.exists(root)) return;
try (Stream<Path> s = Files.walk(root)) {
for (Path p : s.sorted(Comparator.reverseOrder()).toList()) Files.delete(p);
}
}
copyDir(d1, d1_copy);
try (Stream<Path> s = Files.walk(d1_copy)) { s.count(); } // 출력: 7
deleteRecursively(d1_copy);
Files.exists(d1_copy); // 출력: false디렉터리를 register하고 poll/take로 이벤트를 받는다. 파일 하나를 쓰면 ENTRY_CREATE와 ENTRY_MODIFY가 연달아 오는 게 보통이며, 큰 파일은 MODIFY가 여러 번 온다. 처리 후 반드시 key.reset(). 배치에서 "폴더에 파일이 떨어지면 처리" 패턴의 기초다.
static String watchDemo(Path dir) throws Exception {
Path wd = Files.createDirectories(dir.resolve("watch"));
try (WatchService ws = FileSystems.getDefault().newWatchService()) {
wd.register(ws, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
Files.writeString(wd.resolve("n.txt"), "x");
WatchKey key = ws.poll(3, TimeUnit.SECONDS); // 무한 대기는 ws.take()
if (key == null) return "no event";
List<String> ev = new ArrayList<>();
for (WatchEvent<?> e : key.pollEvents()) ev.add(e.kind().name() + ":" + e.context());
key.reset(); // 다음 이벤트 받으려면 필수
return ev.toString();
}
}
// 출력: [ENTRY_CREATE:n.txt, ENTRY_MODIFY:n.txt]배치 결과 파일은 out/2024/01/ 같은 경로에 날짜가 붙은 이름으로 쓴다. 부모 디렉터리를 먼저 만들고, 파일명은 BASIC_ISO_DATE(20240305)로. CSV는 헤더를 skip(1)하고 split으로 컬럼을 뽑는다(따옴표 포함 CSV는 명령어 01 스니펫 참고).
Path out = base.resolve("out/2024/01/r.txt");
Files.createDirectories(out.getParent());
Files.writeString(out, "r");
Files.exists(out); // 출력: true
String name = "orders_" + LocalDate.of(2024, 3, 5).format(DateTimeFormatter.BASIC_ISO_DATE) + ".csv";
// 출력: orders_20240305.csv
Path csv = base.resolve("d.csv");
Files.write(csv, List.of("id,name", "1,Kim", "2,Lee"));
try (Stream<String> s = Files.lines(csv)) {
s.skip(1).map(l -> l.split(",")).map(a -> a[1]).toList();
}
// 출력: [Kim, Lee]고정 포맷 바이너리는 DataOutputStream으로 쓰고 같은 순서로 읽는다. writeUTF는 앞에 2바이트 길이를 붙인다(int 4 + UTF 2+2 + double 8 = 16바이트).
try (var out = new DataOutputStream(new BufferedOutputStream(Files.newOutputStream(bin)))) {
out.writeInt(42); out.writeUTF("hi"); out.writeDouble(1.5);
}
try (var in = new DataInputStream(new BufferedInputStream(Files.newInputStream(bin)))) {
in.readInt() + "/" + in.readUTF() + "/" + in.readDouble();
}
// 출력: 42/hi/1.5
Files.size(bin); // 출력: 16Files.lines/list/walk를 닫지 않음long n = Files.lines(p).count(); // 파일 핸들 누수 (Windows에서는 삭제/이동 실패로 이어짐)
try (Stream<String> s = Files.lines(p)) { long n = s.count(); } // 올바름| 코드 | 결과 |
|---|---|
new FileReader(f) |
플랫폼 기본 인코딩 (환경 의존) |
new FileReader(f, StandardCharsets.UTF_8) |
명시 (JDK 11+) |
props.load(Files.newInputStream(p)) → 한글 |
íê¸ ì 목 (ISO-8859-1) |
props.load(Files.newBufferedReader(p, UTF_8)) → 한글 |
한글 제목 |
Files.readString(eucKrFile) |
MalformedInputException |
Files.writeString(base.resolve("nodir/f.txt"), "x"); // NoSuchFileException
Files.createDirectories(p.getParent()); Files.writeString(p, "x"); // 올바름
Files.copy(src, existing); // FileAlreadyExistsException
Files.copy(src, existing, StandardCopyOption.REPLACE_EXISTING); // 올바름Path.startsWith/endsWith를 문자열 접두/접미로 착각Path p = Path.of("data/logs/app.log");
p.endsWith(".log"); // false — 경로 요소 단위 비교
p.endsWith("app.log"); // true
p.toString().endsWith(".log"); // 문자열 검사는 이렇게String path = dir + "/" + name; // OS 구분자, 중복 슬래시 문제
Path path = Path.of(dir).resolve(name); // 올바름
Path.of("data").resolve("C:/abs/x.txt"); // C:\abs\x.txt — 절대 경로가 오면 그대로 (주의)delete / createDirectory로 중간 경로Files.delete(nonEmptyDir); // DirectoryNotEmptyException
deleteRecursively(nonEmptyDir); // walk 역순 삭제 (스니펫 4)
Files.createDirectory(base.resolve("a/b/c")); // a가 없으면 NoSuchFileException
Files.createDirectories(base.resolve("a/b/c")); // 올바름readAllLines/readString으로 통째 읽기List<String> all = Files.readAllLines(huge); // OutOfMemoryError 위험
try (var r = Files.newBufferedReader(huge)) { String line; while ((line = r.readLine()) != null) process(line); }try (var in = new FileInputStream(f)) { int b; while ((b = in.read()) != -1) ... } // 시스템 콜 폭증
try (var in = new BufferedInputStream(new FileInputStream(f))) { ... } // 8KB 버퍼
// 또는 Files.newInputStream + readAllBytes / transferToBufferedReader r = Files.newBufferedReader(p);
Files.move(p, q); // Windows: FileSystemException (다른 프로세스가 사용 중)
// 스트림을 먼저 close (try-with-resources 블록 밖에서 이동)Files.lines/walk 스트림 조합getBytes/new String)FileTime ↔ Instant 변환