[Java] java.nio.file.Files 사용법 익히기
개요
일을 하다보니 java.nio.file.Files
를 알게 되었다.
꽤나 유용한 거 같아서 이 기회에 간단한 사용법을 익혀보려고 이 글을 썼다.
지금부터 이 패키지에 어떤 기능이 있는지 간단하게 알아보자.
그리고 java.nio.file.Files
로만 하기 힘들거나, 더 심플하게 코딩할 수 있는
다른 API 들도 중간중간 소개하겠다.
참고: 주로 사용하는 패키지 목록. import 시 헷갈리면 여기를 참조하세요!
- import java.util.stream.Collectors;
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.nio.file.Files;
- import java.nio.file.Paths;
- import java.nio.file.attribute.BasicFileAttributes;
- import java.time.Instant;
- import java.time.LocalDateTime;
- import java.time.ZoneId;
- 시간 관련된 건 다 java.time 패키지 하위에 있는 걸 사용한다.
파일 속성 조회
@Test
void FileFindFilteringTest() throws IOException {
String filePath = "D:/upload/wow.txt";
BasicFileAttributes basicFileAttributes
= Files.readAttributes(Paths.get(filePath), BasicFileAttributes.class);
System.out.println("isRegularFile = " + basicFileAttributes.isRegularFile());
System.out.println("creationTime = " + basicFileAttributes.creationTime());
System.out.println("lastModifiedTime = " + basicFileAttributes.lastModifiedTime());
}
출력 결과:
isRegularFile() = true
creationTime() = 2021-12-21T11:03:45.21Z
lastModifiedTime() = 2021-12-21T10:04:22Z
Stream 을 통한 파일 복제
Files.newInputStream(Paths.get(realPath))
Files.newOutputStream(Paths.get(writePath))
@Test
void testMe() {
// 그냥 경로를 지정하는 코드다 신경쓰지 말자
String realPath = "D:/[Java] Read And Write File/123.txt";
String writePath = realPath.substring(0, realPath.lastIndexOf("/")) + "/wow.txt" ;
// 여기서부터 집중
try(InputStream is = Files.newInputStream(Paths.get(realPath));
OutputStream os = Files.newOutputStream(Paths.get(writePath))
) {
// 어떤 java 버전에서도 가능한 정석 코드
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) >= 0) {
os.write(buffer, 0, read);
}
// java 9 이상 사용시 Inputstream 의 transferTo(OutputStream out); 사용
// is.transferTo(os);
// Spring 을 사용 중이라면 아래 2가지 방식 모두 고려해보자.
// StreamUtils.copy(is, os); // is, os 를 close 하지 않음, 내부 버퍼 4096 사용
// FileCopyUtils.copy(is, os); // is, os 를 자동으로 close 함, 내부 버퍼 8192 사용
} catch (Exception e) {
e.printStackTrace();
}
}
위에서 사용한 것은 InputStream, OutputStream 이였다면
이번에는 Reader, Writer로 파일을 제어할 수 있는 방법도 알아보자.
Files.newBufferedReader(Paths.get("파일경로"), StandardCharsets.UTF_8);
Files.newBufferedWriter(Paths.get("파일경로"), StandardCharsets.UTF_8);
이 방법으로 다시 위의 동일한 파일 복제 기능을 구현하려면 아래처럼 코딩한다.
String realPath = "D:/[Java] Read And Write File/123.txt";
String writePath = realPath.substring(0, realPath.lastIndexOf("/")) + "/wow.txt" ;
try(BufferedReader reader = Files.newBufferedReader(Paths.get(realPath), StandardCharsets.UTF_8);
BufferedWriter writer = Files.newBufferedWriter(Paths.get(writePath), StandardCharsets.UTF_8)
) {
// 어떤 java 버전에서도 가능한 정석 코드
char[] buffer = new char[8192];
int read;
while ((read = reader.read(buffer)) >= 0) {
writer.write(buffer, 0, read);
}
} catch (Exception e) {
e.printStackTrace();
}
지금까지 Stream 을 사용한 파일 복제 방법을 알아봤다.
그런데 단순 파일 복제는 아래에서 볼 경로를 통한 파일 복제
방법이 더 편하다.
Stream 방식은 HttpServletRequest
, HttpServletResponse
와 작업을 할 때 더 유용하다.
경로를 통한 파일 복제
정말 간단하다.
public static void main(String[] args) throws IOException {
String realPath = "D:/[Java] Read And Write File/123.txt";
String writePath = realPath.substring(0, realPath.lastIndexOf("/")) + "/wow.txt" ;
Files.copy(Paths.get(realPath), Paths.get(writePath));
}
Files.copy(); 는 오버라이드로 3가지 종류가 있다
Files.copy(Path path, OutputStream out)
Files.copy(InputStream in, Path path, CopyOption... option)
Files.copy(Path path, Path target, CopyOption... option)
대충보면 어떻게 쓰는지 감이 잡히리라 생각한다. 더 이상의 설명은 안하겠다.
참고로 java.nio.file.CopyOption 인자 값으로 몇가지 추가적인 옵션을 줄 수 있다.
다른 블로그에서는 StandardCopyOption.REPLACE_EXISTING 를 흔히 쓰는 걸 봤다. 필요하다면 사용하자.
Directory 복사하기
Files
에는 재귀적으로 Directory 내의 모든 것을 복사하는 기능은 없다.
Directory의 내부 모든 파일을 재귀적으로 복사하고 싶다면
org.springframework.util.FileSystemUtils.copyRecursively(~)
를 사용하자.
파일의 문자열 모두 읽어오기
방법1: Files.newBufferedReader
public static void main(String[] args) {
String temp = "D:/[Java] Read And Write File/123.txt";
try(BufferedReader br = Files.newBufferedReader(Paths.get(temp), StandardCharsets.UTF_8)) {
StringBuilder builder = new StringBuilder();
String str = null;
while((str = br.readLine()) != null) {
builder.append(str).append("\n");
}
System.out.print(builder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
방법2: Files.lines
public static void main(String[] args) {
String temp = "D:/[Java] Read And Write File/123.txt";
try(Stream<String> lines = Files.lines(Paths.get(temp), StandardCharsets.UTF_8)) {
StringBuilder builder = new StringBuilder();
lines.forEach(s -> builder.append(s).append("\n"));
System.out.println(builder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
텍스트 파일에 글 쓰기
public static void main(String[] args) throws IOException {
String temp = "D:/[Java] Read And Write File/123.txt";
ArrayList<String> messageList = new ArrayList<>(Arrays.asList("hello", "world"));
// 방법1: Files.write
// 두번째 파라미터로 Iterable<? extends CharSequence> iterable 를 받는다.
Files.write(Paths.get(temp), messageList, StandardCharsets.UTF_8);
// 방법2: Files.writeString
Files.writeString(Paths.get("C:/upload/jackson/ex1.txt"),
"안녕하세요", StandardCharsets.UTF_8);
}
위 2가지 방법 모두 기존 텍스트 파일의 내용은 다 지워지고, 새로이 작성되는 점 주의하기 바란다.
directory 내의 파일 조회하기
테스트를 위하여 C:\upload\directory1 경로에 아래와 같은 디렉토리 및 파일 구조를 잡았다.
\---directory1
+---directory1_depth1
| | directory1_dept1_file1.txt
| | directory1_dept1_file2.txt
| |
| \---directory1_dept2
| directory1_dept2_file1.txt
| directory1_dept2_file2.txt
|
\---directory2_depth1
directory2_dept1_file1.txt
directory2_dept1_file2.txt
이제 directory 모든 파일들을 조회해보자.
@Test
void FilesWalkTest() {
String directoryPath = "C:/upload/directory1";
List <Path> list = Collections.emptyList();
try(Stream<Path> walk = Files.walk(Paths.get(directoryPath))) {
list = walk.filter(Files::isRegularFile)
.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
list.forEach(System.out::println);
}
출력은 아래와 같이 나온다.
폴더의 depth 를 지정하고 싶다면 Files.walk(Paths.get(directoryPath), 2)
;
처럼 maxDepth를 지정할 수도 있다.
그리고 반드시 사용할 때는 try-with-resource를 사용해서 close 가 되도록 해주자.
API Note:
This method must be used within a try-with-resources statement or similar control structure to ensure that the stream's open directories are closed promptly after the stream's operations have completed.
참고:https://stackoverflow.com/questions/54096143/how-to-close-implicit-stream-in-java
특정 확장자의 파일들만 찾아내기
테스트를 위하여 C:\upload\directory1 경로에 아래와 같은 디렉토리 및 파일 구조를 잡았다.
\---directory1
| dIZwF9gV0Z.jpg
| Hpdf_ghlRA7mJEb.jpg
|
+---directory1_depth1
| | directory1_dept1_file1.txt
| | directory1_dept1_file2.txt
| |
| \---directory1_dept2
| directory1_dept2_file1.txt
| directory1_dept2_file2.txt
|
\---directory2_depth1
directory2_dept1_file1.txt
directory2_dept1_file2.txt
idea64_w4zW6Yf1T3.jpg
idea64_W8HLV3vgyV.jpg
idea64_ZbmjlckX4b.jpg
Java 코드는 아래와 같다.
@Test
void FileFindTest() {
String[] fileExtensions = {"png", "jpg", "gif"};
String directoryPath = "C:/upload/directory1";
Path path = Paths.get(directoryPath);
List<Path> collect = Collections.emptyList();
if (Files.isDirectory(path)) {
try(Stream<Path> walk = Files.walk(path)) {
collect = walk.filter(p -> !Files.isDirectory(p))
.filter(f -> Arrays.stream(fileExtensions)
.anyMatch(s -> f.toString().endsWith(s))
).collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
}
collect.forEach(System.out::println);
}
출력 결과
특정 기간 동안 수정된 파일들만 찾아내기
위에서는 Files.walk
를 사용했지만, 이번에는 Files.find
API를 사용해보자.
아래 코드는 어제 자정(= 00:00)부터 현재까지 변경 이력이 있는 파일 목록을 조회하는 코드다.
/*
디렉토리 구조
C:\UPLOAD\DIRECTORY1
| 23day.png ===> 2021-12-23 에 수정됨
| dodo.png
| lala.png
|
+---directory1_depth1
| | directory1_dept1_file1.txt ===> 2021-12-22 에 수정됨
| | directory1_dept1_file2.txt
| |
| \---directory1_dept2
| directory1_dept2_file1.txt
| directory1_dept2_file2.txt ===> 2021-12-22 에 수정됨
|
+---directory2_depth1
| directory2_dept1_file1.txt
| directory2_dept1_file2.txt
|
\---img
at_12_17(2).png
at_12_17.png
image1.png
*/
public static void main(String[] args) {
// 필터링할 파일을 갖고 있는 디렉토리
Path directoryPath = Paths.get("C:/upload/directory1");
// 앞으로 자주 쓸 DateTimeFormatter 를 선언 및 할당
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZonedDateTime yesterdayTruncate
= ZonedDateTime.now(ZoneId.of("Asia/Seoul")) // 내가 사는 한국 시간 기준으로
.minusDays(1) // 하루를 빼고
.truncatedTo(ChronoUnit.DAYS); // 거기에 자정으로 시간을 맞춘다.
// 참고) 현재 코드를 작성한 시간은 2021-12-23 오후 12 시 29분
System.out.println("today : "
+ ZonedDateTime.now(ZoneId.of("Asia/Seoul")).format(dateFormat));
// 2021-12-22 00:00:00 이 출력된다.
System.out.println("yesterdayTruncate : "
+ yesterdayTruncate.format(dateFormat));
// Instant를 쓰면 TimeZone을 신경 쓰지 않고 시간 연산을 할 수 있기 때문에 편리하다.
Instant yesterdayInstant = yesterdayTruncate.toInstant();
// 필터링 한 파일 목록을 저장할 리스트
List<Path> result = Collections.emptyList();
// Files.find의 인자값은 아래와 같다.
// 첫번째 인자값은 디렉토리명
// 두번째 파라미터는 디렉토리 내부에서 검색할 때 몇 레벨까지 검색할지 결정
// 세번째 인자값은 필터링 콜백
try(Stream<Path> pathStream = Files.find(directoryPath,
Integer.MAX_VALUE,
(path, basicFileAttributes) -> {
// 폴더는 무시 + 읽을 수 없는 파일도 무시
if(Files.isDirectory(path) || !Files.isReadable(path)) {
return false;
}
// 최종 수정된 날짜를 조회
FileTime fileModifedTime = basicFileAttributes.lastModifiedTime();
// 조회된 날짜에서 Instant 를 뽑아냄
Instant fileInstant = fileModifedTime.toInstant();
// 최종적으로 위에서 작성한 yesterdayInstant 와 비교한다.
// 만약 0 이상이면 2021-12-22 00:00:00 시간을 포함한 그 이후의 시간에
// 파일이 수정되었다는 것을 의미한다.
return fileInstant.compareTo(yesterdayInstant) >= 0;
})) {
// 결과를 수집한다.
result = pathStream.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
// 결과를 조회한다.
result.forEach(p -> {
try {
// 속성 조회
BasicFileAttributes readAttributes =
Files.readAttributes(p, BasicFileAttributes.class);
// 파일 수정시간 조회
FileTime lastModifiedTime = readAttributes.lastModifiedTime();
// 수정 시간에서 ZonedDateTime을 뽑아냄
ZonedDateTime zonedDateTime =
lastModifiedTime.toInstant().atZone(ZoneId.of("Asia/Seoul"));
// 출력
System.out.println(String.format("%-100s [%s]",
p.toAbsolutePath().toString(),
zonedDateTime.format(dateFormat)));
} catch (IOException e) {
e.printStackTrace();
}
});
}
Files.find
도 Files.walk
와 마찬가지로 사용할 때는 try-with-resource를 사용해야 한다.
Files.walk
와 Files.find
API는 굉장히 유사하다.
그러니 자신이 더 편리하다 생각하는 API를 사용하면 될 듯하다.
참고로 위 코드에서 ZoneDateTime 과 Instant 에 대해서 잘 모르겠다면
이 블로그를 참고하면 좋다.
Author And Source
이 문제에 관하여([Java] java.nio.file.Files 사용법 익히기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@dailylifecoding/Java-nio-package-Files-usage저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)