CodeGym /행동 /JAVA 25 SELF /java.io와 java.nio의 기본: 차이점과 API의 진화

java.io와 java.nio의 기본: 차이점과 API의 진화

JAVA 25 SELF
레벨 35 , 레슨 0
사용 가능

1. Java가 파일을 다루게 된 과정

Java와 파일: 시작은 어디였나

한때, 1996년에 언어 개발자들은 파일 작업의 첫 접근 — 패키지 java.io를 제안했습니다. 그 안에 File, FileInputStream, FileOutputStream, Reader, Writer 등의 클래스가 등장했죠. 이 클래스들은 파일이 존재하는지, 파일 크기와 수정 날짜를 확인하고, 디렉터리를 다루는 등 다양한 작업을 가능하게 했습니다.

하지만 초창기 버전이 늘 그렇듯 완벽하진 않았습니다 — 더 편하고 안전하게 만들 수 있는 부분이 많았습니다.

오래된 API (java.io)의 한계

  • File — 실제 파일이 아니라 “바로가기(핸들)”에 가깝다. File 클래스는 내용 읽기나 쓰기를 할 수 없습니다. 경로와 메타데이터를 설명할 뿐이죠. 읽기/쓰기를 하려면 별도의 스트림인 FileInputStream/FileOutputStream 또는 Reader/Writer가 필요합니다.
  • 경로 처리 — 고통. "C:\\Users\\" + user + "\\Documents" 같이 수동으로 경로를 이어 붙이면 Windows와 Linux 간에 옮길 때 자주 깨졌습니다.
  • 심볼릭 링크 미지원. 오래된 API는 심링크를 이해하지 못해 올바르지 않게 동작할 수 있었습니다.
  • 속성 지원이 약함. 접근 권한, 소유자, “숨김” 플래그 등을 얻기가 어려웠습니다.
  • 대용량 파일에 비효율적. 클래식 스트림은 대량 데이터에 대한 편리하고 빠른 시나리오를 제공하지 않았고, 비동기 I/O도 없었습니다.

java.nio.file의 등장 (NIO.2, Java 7)

Java 7에서 새로운 패키지인 java.nio.file(흔히 NIO.2)이 등장했습니다. 이것이 가져온 것은:

  • Path — 현대적인 경로 추상화( ZIP, 클라우드, 네트워크 파일 시스템 내부 경로까지 포함 ).
  • Files — 읽기, 쓰기, 복사, 삭제를 위한 정적 유틸리티.
  • FileSystem — 로컬 디스크뿐 아니라 다른 파일 시스템까지 다룸.
  • 심볼릭 링크, 확장 속성, 접근 권한 지원.
  • 비동기 I/O 및 대용량 데이터에서의 개선된 성능.
  • 디렉터리 처리와 스트림 API와의 편리한 연계.

NIO는 “New Input/Output”의 약자이며, 등장한 지 10년도 넘었지만 Java에서는 여전히 현대 표준입니다.

2. 접근 방식 비교: java.io vs java.nio.file

File 클래스 (java.io)

  • 파일 또는 디렉터리의 경로와 그 메타데이터를 나타냅니다.
  • 파일의 내용을 읽거나 쓸 줄 모릅니다.
  • 존재 여부, 크기, 유형(파일/디렉터리), 절대 경로 등을 조회할 수 있습니다.
import java.io.File;

public class ExampleIO {
    public static void main(String[] args) {
        File file = new File("example.txt");
        if (file.exists()) {
            System.out.println("파일이 존재합니다!");
            System.out.println("크기: " + file.length() + " 바이트");
        }
    }
}

Path 클래스 (java.nio.file)

  • 현대적인 경로 추상화로, 자연스럽게 결합하고 정규화할 수 있습니다.
  • 로컬, 아카이브, 네트워크 경로를 모두 표현할 수 있습니다.
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ExampleNIOPath {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        if (Files.exists(path)) {
            System.out.println("파일을 찾았습니다!");
            System.out.println("크기: " + Files.size(path) + " 바이트");
        }
    }
}

Files 클래스 (java.nio.file)

  • 전체/부분 읽기·쓰기를 위한 정적 메서드 모음.
  • 파일 복사, 삭제, 이동.
  • 디렉터리 생성/삭제.
  • 확장 속성 접근.
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class ExampleNIOFiles {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("example.txt");
        List<String> lines = Files.readAllLines(path);
        for (String line : lines) {
            System.out.println(line);
        }
    }
}

비교 표

기능
java.io.File
java.nio.file.Path + Files
경로 표현
내용 읽기/쓰기 아니오 예 (Files 사용)
경로 작업 불편함 편리함 (resolve, normalize)
심볼릭 링크 아니오
파일 속성 제한적 확장됨
비동기 I/O 아니오 예 (NIO.2)
크로스 플랫폼 부분적 탁월함

3. 언제 무엇을 사용할까?

오래된 API (java.io): 언제 필요할까?

  • 레거시 코드 지원용. 프로젝트가 Java 7 이전에 시작되어 곳곳에서 File, FileInputStream, FileOutputStream을 사용한다면 호환성을 유지해야 할 수도 있습니다.
  • 새 프로젝트에서는 — 권장하지 않음. 오늘날 ‘노장’ File을 쓰는 것은 VHS 테이프로 영상을 보는 것과 같습니다.

새 API (java.nio.file): 현대 표준

  • 새 프로젝트에서는 항상 PathFiles를 우선하세요.
  • 더 간단하고, 안전하며, 스트림·람다·try-with-resources와의 통합이 뛰어납니다.

간단한 요약

  • 읽기, 쓰기, 복사, 삭제?Files로.
  • 경로 작업(결합, 정규화)?Path로.
  • 크기, 날짜, 권한?Files.getAttribute() 및 관련 메서드.
  • 디렉터리 순회, 재귀?Files.walk, Files.list.

4. 예제: 오래된 API와 새 API의 코드 비교

예제 1: 파일 존재 여부 확인

예전 방식:

import java.io.File;

File file = new File("data.txt");
if (file.exists()) {
    System.out.println("파일을 찾았습니다!");
}

새로운 방식:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

Path path = Paths.get("data.txt");
if (Files.exists(path)) {
    System.out.println("파일을 찾았습니다!");
}

예제 2: 파일의 모든 줄 읽기

예전 방식:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

File file = new File("data.txt");
BufferedReader reader = new BufferedReader(new FileReader(file));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}
reader.close();

새로운 방식:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

Path path = Paths.get("data.txt");
List<String> lines = Files.readAllLines(path);
for (String line : lines) {
    System.out.println(line);
}

설명: 새로운 방식이 더 짧고 안전합니다. try-with-resources를 사용하면 자원 관리가 더욱 쉬워집니다.

예제 3: 문자열을 파일에 쓰기

예전 방식:

import java.io.FileWriter;

FileWriter writer = new FileWriter("output.txt");
writer.write("안녕, 파일!");
writer.close();

새로운 방식:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

Path path = Paths.get("output.txt");
Files.write(path, "안녕, 파일!".getBytes());

5. 유용한 포인트

왜 Java는 전면 개편을 했을까?

  • 안전성과 신뢰성. 자원 닫기와 경로 처리에서 오류가 줄어듭니다.
  • 크로스 플랫폼. Windows, Linux, 심지어 ZIP 아카이브까지 동일한 클래스로 다룹니다.
  • 확장 용이성. 클라우드와 네트워크 파일 시스템 지원을 추가하기 쉬워집니다.
  • 성능. 대용량 파일에서 더 좋은 동작과 비동기 작업.
  • 현대 Java 스택과의 호환성. 람다, 스트림, try-with-resources.

오래된 API에서 새 API로 전환하려면?

대부분의 경우 FilePath로, 그 반대로도 변환할 수 있습니다:

File file = new File("data.txt");
Path path = file.toPath();
File file2 = path.toFile();

오래된 코드를 마주치면 무엇을 할까?

  • 겁먹지 마세요: 더 간단하고 현대적으로 만들 수 있습니다.
  • 가능하다면 java.nio.file로 다시 작성하세요.
  • 어렵다면 브리지(toPath(), toFile())를 사용해 점진적으로 마이그레이션하세요.

6. 마지막 예: 미니 애플리케이션

예: 파일이 존재하는지 확인하고 그 크기를 출력합니다.

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileInfo {
    public static void main(String[] args) {
        Path path = Paths.get("test.txt");
        if (Files.exists(path)) {
            try {
                long size = Files.size(path);
                System.out.println("파일을 찾았습니다. 크기: " + size + " 바이트");
            } catch (Exception e) {
                System.out.println("파일 크기를 가져오는 중 오류: " + e.getMessage());
            }
        } else {
            System.out.println("파일을 찾을 수 없습니다!");
        }
    }
}

여기서 사용한 것:

  • Path — 경로의 표현.
  • Files.exists() — 존재 여부 확인.
  • Files.size() — 크기 얻기.

7. 파일 API 작업 시 흔한 실수

오류 №1: File이 내용 읽기나 쓰기를 할 수 있다고 기대함. 실제로 File은 “바로가기”일 뿐입니다. 읽기/쓰기를 위해서는 FileInputStream/FileOutputStream(오래된 API) 또는 Files(새 API) 유틸리티를 사용하세요.

오류 №2: + 또는 슬래시로 경로를 수동으로 이어 붙임. 이는 운영체제마다 오류를 일으킵니다. Path.resolve() 또는 여러 인자를 받는 Paths.get(...)을 사용하세요.

오류 №3: 스트림을 닫지 않음. 오래된 API에서는 자원 누수의 흔한 원인입니다. 새 API에서 많은 작업은 Files를 통해 스트림을 만들지 않으며, 스트림이 필요한 경우엔 try-with-resources를 사용하세요.

오류 №4: 새 프로젝트에서 오래된 API를 사용함. 새 프로젝트를 시작한다면 java.nio.file을 선택하세요. java.io는 레거시 코드와의 호환이 필요한 경우에만 의미가 있습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION