[Java 개념노트 61] 파일 입출력 기본 이해하기

[Java 개념노트 61] 파일 입출력 기본 이해하기

안녕하세요. Java 개념노트 시리즈 예순한 번째 글입니다.

지난 글에서는 값이 있을 수도 없을 수도 있는 상황을 안전하게 다루는 Optional 클래스에 대해 정리했습니다. 이번 글에서는 자바 프로그램에서 파일을 읽고 쓰는 기본 흐름인 파일 입출력에 대해 알아보겠습니다.

프로그램을 만들다 보면 텍스트 파일을 저장하거나, 설정 파일을 읽거나, 로그를 남기거나, 사용자가 입력한 데이터를 파일로 보관해야 할 때가 있습니다. 이런 작업을 파일 입출력이라고 합니다. 자바에서는 File, Reader, Writer, BufferedReader, BufferedWriter 등을 사용해서 파일을 다룰 수 있습니다.


1. 파일 입출력이란?

파일 입출력은 프로그램이 파일에 데이터를 쓰거나, 파일에서 데이터를 읽어오는 작업입니다.

구분 의미 예시
출력 프로그램에서 파일로 데이터를 보냅니다. 메모 내용을 memo.txt에 저장
입력 파일에서 프로그램으로 데이터를 가져옵니다. memo.txt 내용을 읽어서 출력

쉽게 말하면
파일 출력은 프로그램이 파일에 글을 쓰는 것이고, 파일 입력은 파일에 적힌 글을 프로그램이 읽어오는 것입니다.

2. 파일 입출력에서 자주 만나는 클래스

입문 단계에서 먼저 알아두면 좋은 파일 입출력 관련 클래스는 다음과 같습니다.

클래스 역할
File 파일이나 폴더의 경로, 존재 여부, 이름 등을 다룹니다.
FileWriter 문자 데이터를 파일에 씁니다.
FileReader 파일에서 문자 데이터를 읽습니다.
BufferedWriter 문자 데이터를 조금 더 효율적으로 파일에 씁니다.
BufferedReader 파일 내용을 줄 단위로 효율적으로 읽습니다.

이번 글에서는 텍스트 파일을 기준으로 기본 흐름을 정리하겠습니다.

3. java.io 패키지

파일 입출력과 관련된 기본 클래스는 대부분 java.io 패키지에 있습니다.

import java.io.File;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.BufferedWriter;
import java.io.BufferedReader;
import java.io.IOException;

파일 작업은 실패할 가능성이 있습니다. 예를 들어 파일이 없거나, 권한이 없거나, 경로가 잘못되었을 수 있습니다. 그래서 파일 입출력 코드에서는 IOException 같은 예외 처리를 자주 사용합니다.

주의
파일 입출력은 외부 파일 시스템과 연결되는 작업이므로 예외 처리를 함께 생각해야 합니다.

4. File 클래스란?

File 클래스는 파일이나 폴더 자체를 다루기 위한 클래스입니다. 파일의 이름, 경로, 존재 여부, 파일인지 폴더인지 등을 확인할 수 있습니다.

import java.io.File;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        System.out.println(file.getName());
        System.out.println(file.getPath());
    }
}

실행 결과는 다음과 비슷합니다.

sample.txt
sample.txt

new File("sample.txt")sample.txt라는 파일 경로를 표현하는 객체를 만든 것입니다. 이 코드만으로 실제 파일 내용이 만들어지는 것은 아닙니다.

5. File 객체는 파일 내용이 아니다

처음 파일 입출력을 배울 때 많이 헷갈리는 부분이 있습니다. File 객체는 파일의 내용을 담고 있는 객체가 아닙니다. 파일이나 폴더의 경로 정보를 다루는 객체에 가깝습니다.

File file = new File("sample.txt");

이 코드는 파일 내용을 읽는 코드가 아닙니다.
sample.txt라는 경로를 표현하는 File 객체를 만든 것입니다.

파일 내용을 읽거나 쓰려면 FileReader, FileWriter, BufferedReader, BufferedWriter 같은 입출력 클래스를 사용해야 합니다.

6. 파일 존재 여부 확인하기

exists()는 파일이나 폴더가 실제로 존재하는지 확인합니다.

import java.io.File;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        if (file.exists()) {
            System.out.println("파일이 존재합니다.");
        } else {
            System.out.println("파일이 존재하지 않습니다.");
        }
    }
}

실행 결과는 현재 폴더에 sample.txt가 있는지에 따라 달라집니다.

파일이 존재하지 않습니다.

7. 파일인지 폴더인지 확인하기

isFile()은 파일인지 확인하고, isDirectory()는 폴더인지 확인합니다.

import java.io.File;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        System.out.println(file.isFile());
        System.out.println(file.isDirectory());
    }
}

sample.txt가 실제 파일이면 isFile()은 true를 반환합니다. 폴더라면 isDirectory()가 true를 반환합니다.

8. 파일 만들기

createNewFile()을 사용하면 실제 파일을 만들 수 있습니다. 파일 생성은 실패할 수 있으므로 예외 처리가 필요합니다.

import java.io.File;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        File file = new File("sample.txt");

        try {
            boolean created = file.createNewFile();

            if (created) {
                System.out.println("파일을 만들었습니다.");
            } else {
                System.out.println("이미 파일이 존재합니다.");
            }
        } catch (IOException e) {
            System.out.println("파일 생성 중 오류가 발생했습니다.");
        }
    }
}

처음 실행하면 파일이 만들어질 수 있고, 이미 파일이 있다면 false가 반환됩니다.

파일을 만들었습니다.

9. FileWriter로 파일에 글 쓰기

FileWriter는 텍스트 파일에 문자 데이터를 쓸 때 사용합니다.

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            FileWriter writer = new FileWriter("sample.txt");

            writer.write("안녕하세요.\n");
            writer.write("Java 파일 출력 예제입니다.");

            writer.close();

            System.out.println("파일 쓰기 완료");
        } catch (IOException e) {
            System.out.println("파일 쓰기 중 오류가 발생했습니다.");
        }
    }
}

실행하면 현재 프로젝트 폴더 기준으로 sample.txt 파일이 만들어지고 내용이 저장됩니다.

파일 쓰기 완료

\n은 줄바꿈을 의미합니다.

10. close()가 필요한 이유

파일을 열어서 사용한 뒤에는 close()로 닫아주는 것이 중요합니다.

파일을 닫지 않으면 데이터가 완전히 저장되지 않거나, 파일 자원이 계속 사용 중인 상태로 남을 수 있습니다.

FileWriter writer = new FileWriter("sample.txt");

writer.write("Java");

writer.close();

기억하기
파일 입출력 객체를 사용했다면 작업이 끝난 뒤 닫아야 합니다. 나중에는 try-with-resources를 사용해서 더 안전하게 닫을 수 있습니다.

11. FileWriter는 기본적으로 덮어쓴다

new FileWriter("sample.txt")는 기존 파일 내용이 있으면 새 내용으로 덮어쓸 수 있습니다.

FileWriter writer = new FileWriter("sample.txt");

writer.write("새로운 내용");

writer.close();

기존 파일에 내용이 있었다면 사라지고 "새로운 내용"으로 바뀔 수 있습니다.

12. 기존 파일에 이어쓰기

기존 파일 내용을 유지하고 뒤에 이어 쓰고 싶다면 FileWriter 생성자의 두 번째 인자로 true를 전달합니다.

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            FileWriter writer = new FileWriter("sample.txt", true);

            writer.write("\n추가된 내용입니다.");

            writer.close();

            System.out.println("파일 이어쓰기 완료");
        } catch (IOException e) {
            System.out.println("파일 이어쓰기 중 오류가 발생했습니다.");
        }
    }
}

new FileWriter("sample.txt", true)에서 true는 append 모드를 의미합니다.

코드 의미
new FileWriter("sample.txt") 기존 내용을 덮어쓸 수 있습니다.
new FileWriter("sample.txt", true) 기존 내용 뒤에 이어 씁니다.

13. FileReader로 파일 읽기

FileReader는 텍스트 파일에서 문자 데이터를 읽을 때 사용합니다.

import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            FileReader reader = new FileReader("sample.txt");

            int data;

            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }

            reader.close();
        } catch (IOException e) {
            System.out.println("파일 읽기 중 오류가 발생했습니다.");
        }
    }
}

read()는 문자 하나를 읽고, 더 이상 읽을 값이 없으면 -1을 반환합니다.

reader.read()
   ↓
문자 하나 읽기
   ↓
더 이상 읽을 문자가 없으면 -1 반환

14. read() 결과가 int인 이유

FileReaderread()는 문자 하나를 읽지만 반환 타입은 int입니다. 이유는 파일의 끝을 나타내는 -1을 표현해야 하기 때문입니다.

int data = reader.read();

if (data != -1) {
    char ch = (char) data;
    System.out.println(ch);
}

읽은 값이 -1이 아니면 문자로 형변환해서 사용할 수 있습니다.

15. BufferedWriter란?

BufferedWriter는 문자 데이터를 파일에 쓸 때 버퍼를 사용해서 더 효율적으로 처리하는 클래스입니다.

입문 단계에서는 FileWriter를 한 번 감싸서 사용하는 출력 도구라고 이해하면 됩니다.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter("sample.txt"));

            writer.write("BufferedWriter 예제입니다.");
            writer.newLine();
            writer.write("줄 단위로 쓰기 좋습니다.");

            writer.close();

            System.out.println("BufferedWriter 쓰기 완료");
        } catch (IOException e) {
            System.out.println("파일 쓰기 중 오류가 발생했습니다.");
        }
    }
}

newLine()은 줄바꿈을 추가할 때 사용합니다.

핵심 포인트
텍스트를 줄 단위로 쓰거나 여러 번 쓸 때는 BufferedWriter를 사용하면 편리합니다.

16. BufferedReader란?

BufferedReader는 파일 내용을 효율적으로 읽기 위한 클래스입니다. 특히 readLine()을 사용해서 파일을 한 줄씩 읽을 수 있습니다.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader("sample.txt"));

            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            reader.close();
        } catch (IOException e) {
            System.out.println("파일 읽기 중 오류가 발생했습니다.");
        }
    }
}

readLine()은 한 줄을 읽고, 더 이상 읽을 줄이 없으면 null을 반환합니다.

reader.readLine()
   ↓
한 줄 읽기
   ↓
더 이상 읽을 줄이 없으면 null 반환

17. FileReader와 BufferedReader 비교

두 클래스는 모두 파일을 읽을 때 사용할 수 있지만 사용 방식이 다릅니다.

구분 FileReader BufferedReader
읽는 단위 문자 단위 줄 단위로 읽기 편함
대표 메서드 read() readLine()
사용 느낌 기본 읽기 도구 더 편리하고 효율적인 읽기 도구

텍스트 파일을 줄 단위로 읽을 때는 BufferedReader를 자주 사용합니다.

18. FileWriter와 BufferedWriter 비교

두 클래스는 모두 파일에 글을 쓸 때 사용할 수 있습니다.

구분 FileWriter BufferedWriter
역할 파일에 문자 쓰기 버퍼를 사용해 더 효율적으로 문자 쓰기
대표 메서드 write() write(), newLine()
사용 느낌 기본 쓰기 도구 여러 줄 쓰기에 편리한 도구

19. try-with-resources란?

try-with-resources는 사용이 끝난 입출력 객체를 자동으로 닫아주는 문법입니다.

파일 입출력에서는 close()를 꼭 호출해야 하는데, 중간에 예외가 발생하면 close()가 실행되지 못할 수도 있습니다. try-with-resources를 사용하면 이런 문제를 줄일 수 있습니다.

try (자원 생성 코드) {
    파일 입출력 코드
} catch (IOException e) {
    예외 처리 코드
}

20. try-with-resources로 파일 쓰기

try-with-resources를 사용하면 close()를 직접 호출하지 않아도 됩니다.

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("sample.txt"))) {
            writer.write("try-with-resources 예제입니다.");
            writer.newLine();
            writer.write("자동으로 close 됩니다.");

            System.out.println("파일 쓰기 완료");
        } catch (IOException e) {
            System.out.println("파일 쓰기 중 오류가 발생했습니다.");
        }
    }
}

try 괄호 안에서 만든 writer는 try 블록이 끝나면 자동으로 닫힙니다.

21. try-with-resources로 파일 읽기

파일 읽기에서도 try-with-resources를 사용할 수 있습니다.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("파일 읽기 중 오류가 발생했습니다.");
        }
    }
}

파일을 한 줄씩 읽고, try 블록이 끝나면 reader가 자동으로 닫힙니다.

추천 흐름
파일 입출력을 배울 때는 처음에 close()의 의미를 이해하고, 실제 코드에서는 try-with-resources를 사용하는 습관을 들이면 좋습니다.

22. 파일 경로 이해하기

파일 입출력에서 경로는 매우 중요합니다. "sample.txt"처럼 파일 이름만 작성하면 보통 현재 실행 위치를 기준으로 파일을 찾거나 만듭니다.

File file = new File("sample.txt");

폴더 안의 파일을 지정할 수도 있습니다.

File file = new File("data/sample.txt");

위 코드는 data 폴더 안의 sample.txt를 의미합니다. 단, data 폴더가 실제로 존재하지 않으면 파일 생성이나 쓰기에서 오류가 발생할 수 있습니다.

23. 폴더 만들기

폴더를 만들 때는 mkdir() 또는 mkdirs()를 사용할 수 있습니다.

메서드 역할
mkdir() 폴더 하나를 만듭니다.
mkdirs() 필요한 상위 폴더까지 함께 만듭니다.
import java.io.File;

public class Main {
    public static void main(String[] args) {
        File folder = new File("data");

        if (!folder.exists()) {
            boolean created = folder.mkdir();

            if (created) {
                System.out.println("폴더를 만들었습니다.");
            }
        }
    }
}

폴더가 없으면 data 폴더를 만듭니다.

24. 파일 쓰기와 읽기 전체 예제

이번에는 파일에 여러 줄을 쓰고, 다시 읽는 전체 예제를 보겠습니다.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        File folder = new File("data");

        if (!folder.exists()) {
            folder.mkdir();
        }

        String filePath = "data/memo.txt";

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("Java 파일 입출력");
            writer.newLine();
            writer.write("BufferedWriter로 파일을 씁니다.");
            writer.newLine();
            writer.write("BufferedReader로 파일을 읽습니다.");

            System.out.println("파일 쓰기 완료");
        } catch (IOException e) {
            System.out.println("파일 쓰기 중 오류가 발생했습니다.");
        }

        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("파일 읽기 중 오류가 발생했습니다.");
        }
    }
}

실행 결과는 다음과 같습니다.

파일 쓰기 완료
Java 파일 입출력
BufferedWriter로 파일을 씁니다.
BufferedReader로 파일을 읽습니다.

먼저 data 폴더를 만들고, memo.txt 파일에 내용을 쓴 뒤 다시 읽어서 출력했습니다.

25. 파일 입출력 사용 시 자주 하는 실수

파일 입출력을 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
File 객체를 만들면 실제 파일이 생긴다고 생각 File은 경로 표현 객체에 가까움 createNewFile()이나 Writer 사용
파일 경로를 잘못 작성 파일을 찾지 못하거나 생성 실패 현재 실행 위치와 폴더 존재 여부 확인
close()를 호출하지 않음 데이터 저장 누락이나 자원 누수 가능 try-with-resources 사용
FileWriter가 기존 내용을 덮어쓰는 것을 모름 기존 파일 내용이 사라질 수 있음 이어쓰기는 new FileWriter(path, true) 사용
readLine() 종료 조건을 잘못 작성 마지막 줄 이후 처리에서 문제 발생 while ((line = reader.readLine()) != null) 사용
IOException 예외 처리를 빼먹음 컴파일 오류 또는 실행 중 문제 try-catch 또는 throws 사용

26. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요. 사용자가 입력한 것처럼 여러 줄의 공부 기록을 파일로 저장한 뒤 다시 읽는 예제입니다.

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        File folder = new File("study");

        if (!folder.exists()) {
            folder.mkdir();
        }

        String filePath = "study/java-note.txt";

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("오늘 공부한 내용");
            writer.newLine();
            writer.write("1. File 클래스");
            writer.newLine();
            writer.write("2. FileWriter와 FileReader");
            writer.newLine();
            writer.write("3. BufferedWriter와 BufferedReader");

            System.out.println("공부 기록 저장 완료");
        } catch (IOException e) {
            System.out.println("공부 기록 저장 중 오류가 발생했습니다.");
        }

        System.out.println("저장된 공부 기록 읽기");

        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("공부 기록 읽기 중 오류가 발생했습니다.");
        }
    }
}

실행 결과는 다음과 같습니다.

공부 기록 저장 완료
저장된 공부 기록 읽기
오늘 공부한 내용
1. File 클래스
2. FileWriter와 FileReader
3. BufferedWriter와 BufferedReader

27. 이번 글 정리

이번 글에서는 자바에서 파일을 읽고 쓰는 파일 입출력 기본을 정리했습니다. 핵심 내용은 다음과 같습니다.

  • 파일 입출력은 프로그램이 파일에 데이터를 쓰거나 파일에서 데이터를 읽어오는 작업이다.
  • File 클래스는 파일이나 폴더의 경로, 존재 여부, 이름 등을 다룬다.
  • File 객체를 만든다고 실제 파일 내용이 자동으로 만들어지는 것은 아니다.
  • exists()는 파일이나 폴더가 존재하는지 확인한다.
  • isFile()은 파일인지 확인하고, isDirectory()는 폴더인지 확인한다.
  • createNewFile()은 새 파일을 만들 때 사용한다.
  • FileWriter는 문자 데이터를 파일에 쓴다.
  • FileReader는 파일에서 문자 데이터를 읽는다.
  • BufferedWriter는 파일에 글을 더 효율적으로 쓰고, newLine()으로 줄바꿈할 수 있다.
  • BufferedReaderreadLine()으로 파일을 한 줄씩 읽을 수 있다.
  • read()는 더 이상 읽을 문자가 없으면 -1을 반환한다.
  • readLine()은 더 이상 읽을 줄이 없으면 null을 반환한다.
  • 입출력 객체는 사용 후 닫아야 한다.
  • try-with-resources를 사용하면 입출력 객체를 자동으로 닫을 수 있다.
  • FileWriter(path, true)를 사용하면 기존 파일 뒤에 이어 쓸 수 있다.
  • 파일 경로와 폴더 존재 여부를 잘 확인해야 한다.

한 줄 요약
파일 입출력은 프로그램과 파일 사이에서 데이터를 주고받는 작업이며, 자바에서는 File, Reader, Writer, BufferedReader, BufferedWriter를 사용해 텍스트 파일을 읽고 쓸 수 있습니다.

다음 글 예고
다음 글에서는 [Java 개념노트 62] BufferedReader와 BufferedWriter 자세히 보기라는 주제로 텍스트 파일을 줄 단위로 읽고 쓰는 방법과 성능을 고려한 입출력 흐름을 더 자세히 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈