[Java] File vs Files

최은창·2025년 10월 30일
post-thumbnail

00. 개요

대부분의 경우 프로그램은 작업 중 특정 파일에 액세스를 해야한다. 예를 들어, 사용자 데이터는 일부 외부 파일에 기록될 수 있고, 이를 읽으려면 프로그램이 데이터 열기, 읽기, 편집 및 닫기 등의 작업을 수행할 수 있어야 한다.

01. File

  • File클래스는 Java 1.0에서 등장한 파일과 디렉터리에 대한 정보를 관리하는 클래스다.
  • File 클래스는 java.io 패키지에 정의되어 있으며 스트림에서 직접 작동하지 않는다.
  • 지금은 과거 호환을 유지하기 위해 남겨둔 기능이다.
  • File 객체를 생성했다고 파일이나 디렉터리가 바로 만들어지는 것이 아니라 메서드를 통해 생성해야 한다.
import java.io.File;
import java.io.IOException;
import java.util.Date;

public class FileMain {
    public static void main(String[] args) throws IOException {
        File file = new File("temp/example.txt"); 
        File directory = new File("temp/exampleDir"); 

		// exists(): 파일이나 디렉터리의 존재 여부를 확인
        System.out.println("File exist: " + file.exists());

		// createNewFile() 새 파일을 생성
        boolean create = file.createNewFile();
        System.out.println("File create: " + create);

		// mkdir(): 새 디렉터리를 생성
        boolean dirCreated = directory.mkdir();
        System.out.println("Directory created: " + dirCreated);
        
		// delete(): 파일이나 디렉터리를 삭제
        boolean deleted = file.delete();
        System.out.println("File deleted = " + deleted);

		// isFile(): 파일인지 확인
        System.out.println("Is file: " + file.isFile());
        
        // isDirectory(): 디렉터리인지 확인
        System.out.println("Is directory : " + directory.isDirectory());
        
        // getName(): 파일이나 디렉터리의 이름을 반환
        System.out.println("File Name: " + file.getName());
        
        // length(): 파일의 크기를 바이트 단위로 반환
        System.out.println("File size: " + file.length() + "byte");

		// renameTo(): 파일의 이름을 변경하거나 이동
        File newFile = new File("temp/newExample.txt");
        boolean renamed = file.renameTo(newFile);
        System.out.println("File renamed: " + renamed);

		// lastModified(): 마지막 수정 시간을 반환
        long lastModified = newFile.lastModified();
        System.out.println("Last modifed: " + new Date(lastModified));
    }
}

참고 : Java에서 다루는 파일과 디렉토리는 운영 체제 수준에서 다루는 것과 서로 다른 객체다.

02. Files

  • Java 1.7에서 File 클래스를 대체할 Files와 Path가 등장했다.
  • 기존 File보다 성능과 편의성이 모두 개선되었다.
    • File 관련 스트림(FileInputStream, FileWriter)의 사용을 고민하기 전에 Files에 있는 기능을 먼저 찾아보자.
  • Files는 직접 생성할 수 없고, static 메서드를 통해 기능을 제공한다.
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public class FilesMain {
    public static void main(String[] args) throws IOException {
        Path file = Path.of("temp/example.txt");
        Path directory = Path.of("temp/exampleDor");

		// exists(): 파일이나 디렉터리의 존재 여부를 확인
        System.out.println("File exists: " + Files.exists(file));
        
        // createFile() 새 파일을 생성
        try {
            Files.createFile(file);
            System.out.println("File created");
        } catch (FileAlreadyExistsException e) {
            System.out.println(file + "File already exists");
        }

		// createDirectories(): 새 디렉터리를 생성
        try {
            Files.createDirectories(directory);
        } catch (FileAlreadyExistsException e) {
            System.out.println(directory + "Directory already exists");
        }

		// delete(): 파일이나 디렉터리를 삭제
        Files.delete(file);
        System.out.println("File deleted");

		// isRegularFile(): 일반 파일인지 확인
        System.out.println("Is regular file: " + Files.isRegularFile(file));
        
        // isDirectory(): 디렉토리인지 확인
        System.out.println("Is directory: " + Files.isDirectory(directory));
        
        // getFileName(): 파일이나 디렉토리의 이름을 반환
        System.out.println("File name: " + file.getFileName());
        
        // size(): 파일의 크기를 바이트 단위로 반환
        System.out.println("File size: " + Files.size(file));

		// move(): 파일의 이름을 변경하거나 이동
        Path newFile = Path.of("temp/newExample.txt");
        Files.move(file, newFile, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("File moved/renamed");

		// getLastModifiedTime(): 마지막으로 수정된 시간을 반환
        System.out.println("Last modify: " + 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());   
    }
}

03. Path

  • 경로(Path)는 파일 시스템에서 파일이나 데렉터리의 고유한 위치를 의미한다.
  • 문자열로 표현하며 디렉터리 트리 계층을 따라 위치를 나타내며 구분자로 분리되는 경로 구성 요소를 통해 각각의 경로를 나타낸다.
  • 구분 문자는 운영체제에 따라서 Unix, Linux에서는 슬래시(/), Windows에서는 역슬레시()를 사용한다.
    • 절대 경로
      • 최상위 경로에서부터 상대 경로를 포함한 경로
      • /Users/IdeaProjects/java-basic/src/../..
    • 정규 경로
      • 최상위 경로로부터 최단 경로를 포함한 경로며 정규 경로는 시스템에서 한 개만 존재한다.
      • /Users//IdeaProjects
    • 상대 경로
      • 현재 위치에서 구분자를 사용한 경로다.
      • ../../java.dmg

File 경로 표시

import java.io.*;

public class FilePath{
	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());
        }
    }
}

Files 경로 표시

public class FilesPath{
	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());
        }
    }
}

04. Files로 문자 읽기

  • 과거 FileReader, FileWriter 등 스트림 클래스를 사용해서 문자 파일을 읽고 썼다.
  • 추가로 모든 문자를 읽기 위해 반복문을 사용해 파일의 끝까지 읽는 과정을 추가해야 했다.
  • 한 줄 단위로 파일을 읽기 위해서는 BufferedReader와 같은 스트림 클래스를 추가해야 했다.
  • 그래서 주로 외부 라이브러리를 가져다 사용했다. -> 지금은 자바에서 지원해준다.

Files - 모든 문자 읽기

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

import static java.nio.charset.StandardCharsets.*;

public class WriteAndReadString {
    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);

    }
}
  • Files.writeString() : 파일에 쓰기
  • Files.readStirng() : 파일에서 모든 문자 읽기

    Files를 사용하면 아주 쉽게 파일에 문자를 스고 읽을 수 있다.

Files - 라인 단위로 읽기

import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.*;

public class LineReadString {
    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));
        }

        Stream<String> lineStream = Files.lines(path, UTF_8);
        lineStream.forEach(line -> System.out.println(line));
        lineStream.close();
        
        /**
        try(Sream<String> lineStream = Files.lines(path, UTF_8)){
            lineStream.forEach(line -> System.out.println(line));
        }
        */

    }
}
  • Files.readAllLines(path)
    • 파일을 한 번에 다 읽고, 라인 단위로 List에 나누어 저장하고 반환한다.
    • 파일 전체를 메모리에 올리기 때문에 파일 크기가 크면 메모리 초과가 날 수 있다.
  • Files.lines(path)
    • 파일을 한 줄 단위로 나누어 읽고, 메모리 사용량을 줄이고 싶다면 이 기능을 사용하면 된다.
    • 한 줄 당 1MB의 용량을 사용한다면 자바는 파일에서 한 번에 1MB의 데이터만 메모리에 올려 처리한다.
    • 이후 다음 줄을 처리하고 기존 1MB의 데이터는 GC 한다.
    • BufferedReader를 통해서도 한 줄 단위로 나누어 처리가 가능하다.

파일 바이트 변환 복사

  • 200MB 파일을 temp/copy.dat미리 만들어 둔다.
import java.io.*;

public class FileByteCopy {
    private static final int FILE_SIZE = 200 * 1024 * 1024; // 200MB

    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("File taken: " + (endTime-startTime) + "ms");
    }
}
  • FileInputStream에서 readAllBytes를 통해 한 번에 모든 데이터를 읽고 write(bytes)를 통해 한 번에 모든 데이터를 저장한다.
  • 파일(copy.dat) -> 자바(byte) -> 파일(copy_new.dat)의 과정을 거친다.
  • 즉 자바가 copy.dat파일의 데이터를 자바 프로세스가 사용하는 메모리에 부른다. 이후 메모리에 있는 데이터를 copy_new.dat에 전달한다.

TransferTo를 이용한 복사

import java.io.*;

public class FileCopyMainV2 {
    private static final int FILE_SIZE = 200 * 1024 * 1024; // 200MB

    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("File taken: " + (endTime-startTime) + "ms");
    }
}
  • InputStream에는 transferTo()라는 메서드가 있다.(자바 9부터 지원)
  • 이 메서드는 InputStream에서 읽은 데이터를 바로 OutputStream으로 출력한다.
  • transferTo()는 성능 최적화가 되어 있기 때문에 바이트 변환 복사 보다 조금 더 빠르다.
    • 하지만 상황에 따라 조금 더 느릴 수도 있다.
       byte[] bytes = fis.readAllBytes();
       fos.write(bytes);
       // 위 코드가 아래 한줄 코드로 압축
       fis.transferTo(fos);

Files - copy()를 이용한 복사


import java.io.*;
import java.nio.file.*;

public class FilesCopy{
    private static final int FILE_SIZE = 200 * 1024 * 1024; // 200MB

    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("File taken: " + (endTime-startTime) + "ms");
    }
}
  • Files.copy()
    • 자바에 파일 데이털르 불러오지 않고 운영체제의 파일 복사 기능을 사용한다.
    • 파일(copy.dat) -> 파일(copy_new.dat)의 과정을 거친다.
    • 빠르다
  • 해당 기능은 파일을 파일로 복사할 때만 유용하다.
profile
비슷한 어려움을 겪는 누군가에게 도움이 되길

0개의 댓글