일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- container
- 리스트
- 도커
- java socket
- 멀티 쓰레드
- 컨테이너
- Docker
- 스레드 제어와 생명 주기
- Kubernetes
- java network
- 자바 입출력 스트림
- 시작하세요 도커 & 쿠버네티스
- Collection
- Thread
- 인프런
- 쓰레드
- java
- 실전 자바 고급 1편
- 동시성
- 김영한
- 자바 io 보조스트림
- filewriter filereader
- LIST
- 도커 엔진
- 스레드
- 쿠버네티스
- 알고리즘
- Java IO
- 자료구조
- 자바
- Today
- Total
쌩로그
[JAVA] 김영한의 실전 자바 고급 2편 - Se06. File, Files 본문
목차
- 포스팅 개요
- 본론
2-1. File
2-2. File2
2-3. 경로 표시
2-4. Files로 문자 파일 읽기
2-5. 파일 복사 최적화 - 요약
1. 포스팅 개요
해당 포스팅은 김영한의 실전 자바 고급 2편 Section 6의 File, Files
에 대한 학습 내용이다.
학습 레포 URL : https://github.com/SsangSoo/inflearn-holyeye-java-adv2 (해당 레포는 완강시 public으로 전환 예정이다.)
2. 본론
2-1. File
자바에서 파일 또는 디렉토리를 다룰 때는 File
또는 Files
, Path
클래스를 사용하면 된다.
이 클래스들을 사용하면 파일이나 폴더를 생성하고, 삭제하고, 또 정보를 확인할 수 있다.
먼저 File
클래스를 사용해보자.
import java.io.File;
import java.io.IOException;
import java.util.Date;
public class OldFileMain {
public static void main(String[] args) throws IOException {
File file = new File("temp/example.txt");
File directory = new File("temp/exampleDir");
// 1. exists(): 파일이나 디렉터리의 존재 여부를 확인
System.out.println("File exists: " + file.exists());
// 2. createNewFile(): 새 파일을 생성
boolean created = file.createNewFile();
System.out.println("File created: " + created);
// 3. mkdir(): 새 디렉토리를 생성
boolean dirCreated = directory.mkdir();
System.out.println("Directory created: " + dirCreated);
// 4. delete(): 파일이나 디렉토리를 삭제
// boolean deleted = file.delete();
// System.out.println("File deleted: " + deleted);
// 5. isFile(): 파일인지 확인
System.out.println("Is file: " + file.isFile());
// 6. isDirectory(): 디렉토리인지 확인
System.out.println("Is directory: " + directory.isDirectory());
// 7. getName(): 파일이나 디렉토리의 이름을 반환
System.out.println("File Name: " + file.getName());
// 8. length(): 파일의 크기를 바이트 단위로 반환
System.out.println("File size: " + file.length() + "bytes");
// 9. renameTo(File dest): 파일의 이름을 변경하거나 이동
File newFile = new File("temp/newExample.txt");
boolean renamed = file.renameTo(newFile);
System.out.println("File rename: " + renamed);
// 10. lastModified(): 마지막으로 수정된 시간을 반환
long lastModified = newFile.lastModified();
System.out.println("Last modified: " + new Date(lastModified));
}
}
실행 결과
File exists: false
File created: true
Directory created: false
Is file: true
Is directory: true
File Name: example.txt
File size: 0bytes
File rename: false
Last modified: Sat May 03 23:16:16 KST 2025
temp/example.txt
파일 생성temp/exampleDir
폴더 생성
File
은 파일과 디렉토리를 둘 다 다룬다.
참고로 File
객체를 생성했다고 파일이나 디렉토리가 바로 만들어지는 것은 아니다.
메서드를 통해 생성해야 한다.
File
과 같은 클래스들은 학습해야할 중요한 원리가 있는 것이 아니라, 다양한 기능의 모음을 제공한다.
이런 클래스의 기능들은 외우기 보다는 이런 것들이 있다 정도만 간단히 알아두고, 필요할 때 찾아서 사용하면 된다.
2-2. Files
File, Files의 역사
자바 1.0에서 File
클래스가 등장했다.
이후에 자바 1.7에서 File
클래스를 대체할 Files
와 Path
가 등장했다.
Files의 특징
- 성능과 편의성이 모두 개선되었다.
File
은 과거의 호환을 유지하기 위해 남겨둔 기능이다.- 이제는
Files
사용을 먼저 고려하자.
- 이제는
- 여기에는 수 많은 유틸리티 기능이 있다.
File
클래스는 물론이고,File
과 관련된 스트림 (FileInputStream
,FileWriter
)의 사용을 고민하기 전에Files
에 있는 기능을 먼저 찾아보자.- 성능도 좋고, 사용하기도 더 편리하다.
- 기능이 너무 많기 때문에 주요 기능만 알아보고, 나머지는 필요할 때 검색하면 된다.
- 이렇게 기능 위주의 클래스는 외우는 것이 아니다.
- 이런게 있다 정도의 주요 기능만 알아두고, 나머지는 필요할 때 검색하면 된다.
앞서 작성한 코드를 Files
로 그대로 사용해보자.
참고로 Files
를 사용할 때 파일이나, 디렉토리의 경로는 Path
클래스를 사용해야 한다.
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class NewFileMain {
public static void main(String[] args) throws IOException {
Path file = Path.of("temp/example.txt");
Path directory = Path.of("temp/exampleDir");
// 1. exists(): 파일이나 디렉토리의 존재 여부를 확인
System.out.println("File exists: " + Files.exists(file));
// 2. createFile(): 새 파일을 생성
try {
Files.createFile(file);
System.out.println("File created");
} catch (FileAlreadyExistsException e) {
System.out.println(file + " File already exists");
}
// 3. createDirectory(): 새 디렉토리를 생성
try {
Files.createDirectory(directory);
} catch (FileAlreadyExistsException e) {
System.out.println(directory + " Directory already exists");
}
// 4. delete(): 파일이나 디렉토리를 삭제
//Files.delete(file);
//System.out.println("File deleted");
// 5. isRegularFile(): 일반 파일인지 확인
System.out.println("Is regular file: " + Files.isRegularFile(file));
// 6. isDirectory(): 디렉토리인지 확인
System.out.println("Is directory: " + Files.isDirectory(directory));
// 7. getFileName(): 파일이나 디렉토리의 이름을 반환
System.out.println("File name: " + file.getFileName());
// 8. size(): 파일의 크기를 바이트 단위로 반환
System.out.println("File size: " + Files.size(file) + " bytes");
// 9. move(): 파일의 이름을 변경하거나 이동
Path newFile = Path.of("temp/newExample.txt");
Files.move(file, newFile, StandardCopyOption.REPLACE_EXISTING);
System.out.println("File moved/renamed");
// 10. getLastModifiedTime(): 마지막으로 수정된 시간을 반환
System.out.println("Last modified: " + Files.getLastModifiedTime(newFile));
// 추가: readAttributes(): 파일의 기본 속성들을 한 번에 읽기
BasicFileAttributes attrs = Files.readAttributes(newFile, BasicFileAttributes.class);
System.out.println("==== Attributes ====");
System.out.println("Creation time: " + attrs.creationTime());
System.out.println("Is Directory: " + attrs.isDirectory());
System.out.println("Is regular file: " + attrs.isRegularFile());
System.out.println("Is symbolic link: " + attrs.isSymbolicLink());
System.out.println("Size: " + attrs.size());
}
}
Files
는 직접 생성할 수 없고, static 메서드를 통해 기능을 제공한다.
실행 결과
File exists: false
File created
temp\exampleDir Directory already exists
Is regular file: true
Is directory: true
File name: example.txt
File size: 0 bytes
File moved/renamed
Last modified: 2025-05-03T14:33:05.0542228Z
==== Attributes ====
Creation time: 2025-05-03T14:13:19.1907475Z
Is Directory: false
Is regular file: true
Is symbolic link: false
Size: 0
2-3. 경로 표시
파일이나 디렉토리가 있는 경로는 크게 절대 경로와 정규 경로로 나눌 수 있다.
File 경로 표시
import java.io.File;
import java.io.IOException;
public class OldFilePath {
public static void main(String[] args) throws IOException {
File file = new File("temp/..");
System.out.println("path = " + file.getPath());
// 절대 경로
System.out.println("Absolute path = " + file.getAbsolutePath());
// 정규 경로
System.out.println("Canonical path = " + file.getCanonicalPath());
File[] files = file.listFiles();
for (File f : files) {
System.out.println((f.isFile() ? "F" : "D") + " | " + f.getName());
}
}
}
실행 결과
path = temp\..
Absolute path = C:\Users\kss10\IdeaProjects\MyEveryThingThatWithJava\inflean-holyeye-java-adv2\temp\..
Canonical path = C:\Users\kss10\IdeaProjects\MyEveryThingThatWithJava\inflean-holyeye-java-adv2
D | .git
F | .gitignore
D | .idea
F | inflean-holyeye-java-adv2.iml
D | out
D | src
D | temp
- 절대 경로(Absolute path): 절대 경로는 경로의 처음부터 내가 입력한 모든 경로를 다 표현한다.
- 정규 경로(Canonical path): 경로의 계산이 모두 끝난 경로이다. 정규 경로는 하나만 존재한다.
- 예제에서
..
은 바로 위의 상위 디렉토리를 뜻한다.- 이런 경로의 계산을 모두 처리하면 하나의 경로만 남는다.
- 예를 들어 절대 경로는 다음 2가지 경로가 모두 가능하지만
/Users/yh/study/inflearn/java/java-adv2
/Users/yh/study/inflearn/java/java-adv2/temp/..
- 정규 경로는 다음 하나만 가능하다.
/Users/yh/study/inflearn/java/java-adv2
- 예제에서
file.listFiles()
- 현재 경로에 있는 모든 파일 또는 디렉토리를 반환한다.
- 파일이면 F, 디렉토리면 D로 표현하도록 했다.
Files 경로 표시
앞의 예시를 Files
버전으로 작성한 코드이다.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public class NewFliesPath {
public static void main(String[] args) throws IOException {
Path path = Path.of("temp/..");
System.out.println("path = " + path);
// 절대 경로
System.out.println("Absolute path = " + path.toAbsolutePath());
// 정규 경로
System.out.println("Canonical path = " + path.toRealPath());
Stream<Path> pathStream = Files.list(path);
List<Path> list = pathStream.toList();
pathStream.close();
for (Path p : list) {
System.out.println((Files.isRegularFile(p) ? "F" : "D") + " | " + p.getFileName());
}
}
}
실행 결과
path = temp\..
Absolute path = C:\Users\kss10\IdeaProjects\MyEveryThingThatWithJava\inflean-holyeye-java-adv2\temp\..
Canonical path = C:\Users\kss10\IdeaProjects\MyEveryThingThatWithJava\inflean-holyeye-java-adv2
D | .git
F | .gitignore
D | .idea
F | inflean-holyeye-java-adv2.iml
D | out
D | src
D | temp
Files.list(path)
- 현재 경로에 있는 모든 파일 또는 디렉토리를 반환한다.
Stream
이라는 객체는 람다와 스트림에서 따로 학습한다.- 여기서는
toList()
를 통해 우리가 잘 아는List
컬렉션으로 변경해서 사용했다.
- 파일이면 F, 디렉토리면 D로 표현하도록 했다.
2-4. Files로 문자 파일 읽기
문자로된 파일을 읽고 쓸 때 과거에는 FileReader
, FileWriter
같은 복잡한 스트림 클래스를 사용해야 했다.
거기에 모든 문자를 읽으려면 반복문을 사용해서 파일의 끝까지 읽어야 하는 과정을 추가해야 한다.
또 한 줄 단위로 파일을 읽으려면 BufferedReader
와 같은 스트림 클래스를 추가해야 했다.
Files
는 이런 문제를 코드 한 줄로 깔끔하게 해결해준다
Files - 모든 문자 읽기
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.nio.charset.StandardCharsets.*;
public class RealTextFileV1 {
private static final String PATH = "temp/hello2.txt";
public static void main(String[] args) throws IOException {
String writeString = "abc\n가나다";
System.out.println("== Write String==");
System.out.println(writeString);
Path path = Path.of(PATH);
// 파일에 쓰기
Files.writeString(path, writeString, UTF_8);
// 파일에서 읽기
String readString = Files.readString(path, UTF_8);
System.out.println("== Read String ==");
System.out.println(readString);
}
}
실행 결과
== Write String==
abc
가나다
== Read String ==
abc
가나다
Files.writeString()
: 파일에 쓰기Files.readString()
: 파일에서 모든 문자 읽기
Files
를 사용하면 아주 쉽게 파일에 문자를 쓰고 읽을 수 있다
Files - 라인 단위로 읽기
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class RealTextFileV2 {
private static final String PATH = "temp/hello2.txt";
public static void main(String[] args) throws IOException {
String writeString = "abc\n가나다";
System.out.println("== Write String==");
System.out.println(writeString);
Path path = Path.of(PATH);
// 파일에 쓰기
Files.writeString(path, writeString, UTF_8);
// 파일에서 읽기
System.out.println("== Read String ==");
List<String> lines = Files.readAllLines(path, UTF_8);
for(int i = 0; i < lines.size(); i++) {
System.out.println((i + 1) + ":" + lines.get(i));
}
}
}
실행 결과
== Write String==
abc
가나다
== Read String ==
1:abc
2:가나다
Files.readAllLines(path)
- 파일을 한 번에 다 읽고, 라인 단위로
List
에 나누어 저장하고 반환한다. - 100만 줄이면 100만줄을 한 번에 메모리에 올린다.
Files.lines(path)
- 파일을 한 줄 단위로 나누어 읽고, 메모리 사용량을 줄이고 싶다면 이 기능을 사용하면 된다.
- 다만 이 기능을 제대로 이해하려면 람다와 스트림을 알아야 한다.
try(Stream<String> lineStream = Files.lines(path, UTF_8)) {
lineStream.forEach(line -> System.out.println(line));
}
- 파일을 스트림 단위로 나누어 조회한다. (I/O 스트림이 아니라, 람다와 스트림에서 사용하는 스트림이다)
- 파일이 아주 크다면 한 번에 모든 파일을 다 메모리에 올리는 것 보다, 파일을 부분 부분 나누어 메모리에 올리는 것이 더 나은 선택일 수 있다.
- 예를 들어 파일의 크기가 1000MB라면 한 번에 1000MB의 파일이 메모리에 불러진다.
- 앞서
Files.readAllLines
의 경우List
에 1000MB의 파일이 모두 올라간다.
- 앞서
- 이 기능을 사용하면 파일을 한 줄 단위로 메모리에 올릴 수 있다.
- 한 줄 당 1MB의 용량을 사용한다면 자바는 파일에서 한 번에 1MB의 데이터만 메모리에 올려 처리한다.
- 그리고 처리가 끝나면 다음 줄을 호출하고, 기존에 사용한 1M의 데이터는 GC한다.
- 용량이 아주 큰 파일을 처리해야 한다면 이런 방식으로 나누어 처리하는 것이 효과적이다.
- 참고로 용량이 너무 커서 자바 메모리에 한 번에 불러오는 것이 불가능할 수 있다.
- 그때는 이런 방식으로 처리해야만 한다.
- 참고로 용량이 너무 커서 자바 메모리에 한 번에 불러오는 것이 불가능할 수 있다.
- 물론
BufferedReader
를 통해서도 한 줄 단위로 이렇게 나누어 처리하는 것이 가능하다.- 여기서 핵심은 매우 편리하게 문자를 나누어 처리하는 것이 가능하다는 점이다.
- 이 부분은 이후에 람다와 스트림을 학습해야 제대로 이해할 수 있다.
2-5. 파일 복사 최적화
이번에는 파일을 복사하는 효율적인 방법에 대해서 알아보자.
예제를 위해서 200MB 임시 파일을 하나 만들어보자.
예제 파일 생성
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateCopyFile {
private static final int FILE_SIZE = 200 * 1024 * 1024; // 200MB
public static void main(String[] args) throws IOException {
String fileName = "temp/copy.dat";
long startTime = System.currentTimeMillis();
FileOutputStream fos = new FileOutputStream(fileName);
byte[] buffer = new byte[FILE_SIZE];
fos.write(buffer);
fos.close();
long endTime = System.currentTimeMillis();
System.out.println("File created: " + fileName);
System.out.println("File size: " + FILE_SIZE / 1024 / 1024 + "MB");
System.out.println("Time taken: " + (endTime - startTime) + "ms");
}
}
실행 결과
File created: temp/copy.dat
File size: 200MB
Time taken: 420ms
이렇게 만든 임시 파일을 다른 파일로 복사해보자.
파일 복사 예제1
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyMainV1 {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("temp/copy.dat");
FileOutputStream fos = new FileOutputStream("temp/copy_new.dat");
byte[] bytes = fis.readAllBytes();
fos.write(bytes);
fis.close();
fos.close();
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime) + "ms");
}
}
실행 결과
Time taken: 598ms
FileInputStream
에서readAllBytes
를 통해 한 번에 모든 데이터를 읽고write(bytes)
를 통해 한 번에 모든 데이터를 저장한다.- 파일(copy.dat) -> 자바(byte) -> 파일(copy_new.dat)의 과정을 거진다.
- 자바가
copy.dat
파일의 데이터를 자바 프로세스가 사용하는 메모리에 불러온다.- 그리고 메모리에 있는 데이터를
copy_new.dat
에 전달한다.
- 그리고 메모리에 있는 데이터를
파일 복사 예제2
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyMainV2 {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("temp/copy.dat");
FileOutputStream fos = new FileOutputStream("temp/copy_new.dat");
fis.transferTo(fos);
fis.close();
fos.close();
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime) + "ms");
}
}
실행 결과
Time taken: 260ms
InputStream
에는transferTo()
라는 특별한 메서드가 있다. (자바 9)- 이 메서드는
InputStream
에서 읽은 데이터를 바로OutputStream
으로 출력한다. transferTo()
는 성능 최적화가 되어 있기 때문에, 앞의 예제와 비슷하거나 조금 더 빠르다.- 상황에 따라 조금 더 느릴 수도 있다.
- 참고로 디스크는 실행시 시간의 편차가 심하다는 점을 알아두자.
- 파일(copy.dat) -> 자바(byte) -> 파일(copy_new.dat)의 과정을 거진다.
transferTo()
덕분에 매우 편리하게 InputStream
의 내용을 OutputStream
으로 전달할 수 있었다.
파일 복사 예제3
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class FileCopyMainV3 {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis();
Path source = Path.of("temp/copy.dat");
Path target = Path.of("temp/copy_new.dat");
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime) + "ms");
}
}
실행 결과
Time taken: 118ms
Files.copy()
앞의 예제들은 파일을 복사할 때 다음 과정을 거쳤다.
- 파일(copy.dat) -> 자바(byte) -> 파일(copy_new.dat)
이 과정들은 파일의 데이터를 자바로 불러오고 또 자바에서 읽은 데이터를 다시 파일에 전달해야 한다.
Files.copy()
는 자바에 파일 데이터를 불러오지 않고, 운영체제의 파일 복사 기능을 사용한다.
따라서 다음과 같이 중간 과정이 생략된다.
- 파일(copy.dat) -> 파일(copy_new.dat)
따라서 가장 빠르다.
파일을 다루어야 할 일이 있다면 항상 Files
의 기능을 먼저 찾아보자.
물론 이 기능은 파일에서 파일을 복사할 때만 유용하다.
만약 파일의 정보를 읽어서 처리해야 하거나, 스트림을 통해 네트워크에 전달해야 한다면 앞서 설명한 스트림을 직접 사용해야 한다.
3. 요약
FileReader
, FileWriter
로 복잡하게 파일을 읽고 쓰던 불편함을 Files
를 통해서 간편하게 읽고 쓸 수 있도록 알아보았다.
추후 필요할 때 참고하거나 검색해서 사용하면 좋을 거 같다.
그리고 현재 실무에서 파일을 읽고 자주 확인할 때마다 GPT한테 코드르 작성해달라고 하고 복사 붙여넣기를 했는데, 이번에 배운 내용을 토대로 좀 더 코드를 잘 이해할 수 있을 거 같다.
여차하면 내가 짜야지 ㅎㅎ..
'Language > JAVA' 카테고리의 다른 글
[JAVA] 김영한의 실전 자바 고급 2편 - Se08. 네트워크 - 프로그램1 (3) | 2025.05.05 |
---|---|
[JAVA] 김영한의 실전 자바 고급 2편 - Se07. 네트워크 - 기본 이론 (0) | 2025.05.05 |
[JAVA] 김영한의 실전 자바 고급 2편 - Se05. I/O 활용 (1) | 2025.05.02 |
[JAVA] 김영한의 실전 자바 고급 2편 - Se04. IO 기본2 (1) | 2025.05.02 |
[JAVA] 김영한의 실전 자바 고급 2편 - Se03. I/O 기본 1 (2) | 2025.05.01 |