[Java 개념노트 62] BufferedReader와 BufferedWriter 자세히 보기

[Java 개념노트 62] BufferedReader와 BufferedWriter 자세히 보기

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

지난 글에서는 자바에서 파일을 읽고 쓰는 기본 흐름인 파일 입출력에 대해 정리했습니다. 이번 글에서는 텍스트 파일을 더 편리하고 효율적으로 다룰 때 자주 사용하는 BufferedReaderBufferedWriter를 자세히 알아보겠습니다.

파일 입출력은 단순히 파일을 열고 닫는 것만으로 끝나지 않습니다. 실제로는 파일을 한 줄씩 읽거나, 여러 줄을 저장하거나, 기존 파일 뒤에 내용을 추가하거나, 작업이 끝난 뒤 안전하게 자원을 닫아야 합니다. 이때 BufferedReaderBufferedWriter를 사용하면 코드가 더 깔끔해집니다.


1. BufferedReader란?

BufferedReader는 문자 데이터를 효율적으로 읽기 위한 클래스입니다. 특히 텍스트 파일을 한 줄씩 읽을 때 자주 사용합니다.

기본적인 FileReader는 문자 단위로 읽는 도구에 가깝습니다. 반면 BufferedReader는 내부 버퍼를 사용해서 데이터를 모아두고 읽기 때문에 줄 단위 처리에 편리합니다.

쉽게 말하면
BufferedReader는 파일 내용을 한 글자씩 힘들게 읽는 대신, 버퍼를 이용해 더 효율적으로 읽고 readLine()으로 한 줄씩 꺼낼 수 있게 해주는 클래스입니다.

2. BufferedWriter란?

BufferedWriter는 문자 데이터를 효율적으로 쓰기 위한 클래스입니다. 텍스트 파일에 여러 줄을 저장할 때 자주 사용합니다.

FileWriter만으로도 파일에 글을 쓸 수 있지만, BufferedWriter를 함께 사용하면 버퍼를 통해 더 효율적으로 쓰고, newLine()으로 줄바꿈도 편하게 처리할 수 있습니다.

핵심 포인트
파일을 줄 단위로 읽을 때는 BufferedReader, 파일에 여러 줄을 쓸 때는 BufferedWriter를 먼저 떠올리면 좋습니다.

3. 왜 Buffered를 사용할까?

파일 입출력은 프로그램 내부 메모리만 다루는 작업보다 상대적으로 느릴 수 있습니다. 파일 시스템과 데이터를 주고받아야 하기 때문입니다.

버퍼는 데이터를 바로바로 조금씩 처리하는 대신, 일정량을 임시 공간에 모아두었다가 처리하는 방식입니다. 이렇게 하면 입출력 횟수를 줄일 수 있어 효율적입니다.

버퍼 없이 처리
문자 하나 읽기 → 처리
문자 하나 읽기 → 처리
문자 하나 읽기 → 처리

버퍼 사용
데이터를 일정량 모아서 읽기
        ↓
필요할 때 줄 단위로 꺼내기

입문 단계에서는 “버퍼를 쓰면 파일을 더 편하고 효율적으로 읽고 쓸 수 있다” 정도로 이해하면 충분합니다.

4. BufferedReader와 FileReader 관계

BufferedReader는 보통 FileReader를 감싸서 사용합니다.

BufferedReader reader = new BufferedReader(new FileReader("sample.txt"));

위 코드는 다음처럼 이해하면 됩니다.

FileReader
파일에서 문자 데이터를 읽는 기본 통로

BufferedReader
FileReader를 감싸서 더 편리하게 읽도록 도와주는 보조 통로
클래스 역할
FileReader 파일에서 문자 데이터를 읽는 기본 클래스입니다.
BufferedReader 버퍼를 사용하고, readLine()으로 줄 단위 읽기를 제공합니다.

5. BufferedWriter와 FileWriter 관계

BufferedWriter도 보통 FileWriter를 감싸서 사용합니다.

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

위 코드는 다음처럼 이해할 수 있습니다.

FileWriter
파일에 문자 데이터를 쓰는 기본 통로

BufferedWriter
FileWriter를 감싸서 더 편리하고 효율적으로 쓰도록 도와주는 보조 통로
클래스 역할
FileWriter 파일에 문자 데이터를 쓰는 기본 클래스입니다.
BufferedWriter 버퍼를 사용하고, newLine()으로 줄바꿈을 편하게 처리합니다.

6. BufferedReader import

BufferedReaderFileReader를 사용하려면 다음 import가 필요합니다.

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

파일 읽기 작업은 실패할 수 있으므로 IOException 예외 처리도 함께 고려해야 합니다.

7. BufferedWriter import

BufferedWriterFileWriter를 사용하려면 다음 import가 필요합니다.

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

파일 쓰기 작업도 경로 문제, 권한 문제, 디스크 문제 등으로 실패할 수 있으므로 예외 처리를 작성해야 합니다.

8. BufferedWriter로 파일 쓰기

먼저 BufferedWriter를 사용해서 파일에 글을 저장해보겠습니다.

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("memo.txt"));

            writer.write("Java 파일 입출력");
            writer.newLine();
            writer.write("BufferedWriter로 파일을 씁니다.");

            writer.close();

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

실행하면 현재 실행 위치에 memo.txt 파일이 만들어지고 내용이 저장됩니다.

파일 쓰기 완료

9. write() 메서드

write()는 파일에 문자열을 쓸 때 사용합니다.

writer.write("파일에 저장할 내용");

write()는 문자열을 파일에 기록하지만, 자동으로 줄바꿈을 해주지는 않습니다. 줄바꿈이 필요하면 newLine()을 사용하거나 문자열 안에 \n을 넣어야 합니다.

writer.write("첫 번째 줄");
writer.write("두 번째 줄");

위 코드는 줄바꿈 없이 이어서 저장될 수 있습니다.

첫 번째 줄두 번째 줄

10. newLine()으로 줄바꿈하기

BufferedWriternewLine()은 줄바꿈을 추가하는 메서드입니다.

writer.write("첫 번째 줄");
writer.newLine();
writer.write("두 번째 줄");

파일에는 다음처럼 저장됩니다.

첫 번째 줄
두 번째 줄

운영체제마다 줄바꿈 방식이 조금 다를 수 있기 때문에, 직접 \n을 쓰는 것보다 newLine()을 사용하는 것이 더 깔끔할 때가 많습니다.

추천
텍스트 파일에 여러 줄을 저장할 때는 write()newLine()을 함께 사용하면 읽기 좋은 코드가 됩니다.

11. BufferedReader로 파일 읽기

이번에는 BufferedReader로 파일을 읽어보겠습니다.

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("memo.txt"));

            String line;

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

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

memo.txt에 저장된 내용이 다음과 같다면,

Java 파일 입출력
BufferedWriter로 파일을 씁니다.

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

Java 파일 입출력
BufferedWriter로 파일을 씁니다.

12. readLine() 메서드

readLine()은 파일에서 한 줄을 읽는 메서드입니다. 더 이상 읽을 줄이 없으면 null을 반환합니다.

String line = reader.readLine();

보통은 반복문과 함께 사용합니다.

String line;

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

흐름은 다음과 같습니다.

readLine()으로 한 줄 읽기
        ↓
읽은 줄이 null이 아니면 처리
        ↓
다음 줄 읽기
        ↓
더 이상 읽을 줄이 없으면 null
        ↓
반복 종료

13. readLine()은 줄바꿈 문자를 포함하지 않는다

readLine()은 한 줄을 읽지만, 줄 끝의 줄바꿈 문자는 포함하지 않습니다.

파일 내용

Java
Spring

readLine()으로 읽으면 첫 번째 결과는 "Java", 두 번째 결과는 "Spring"입니다. 각 문자열 안에 줄바꿈 문자가 들어 있는 것은 아닙니다.

따라서 읽은 내용을 다시 파일에 쓸 때 줄바꿈이 필요하면 newLine()을 직접 호출해야 합니다.

14. 파일을 읽으면서 줄 번호 출력하기

파일을 한 줄씩 읽을 때 줄 번호를 함께 출력할 수 있습니다.

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("memo.txt"))) {
            String line;
            int lineNumber = 1;

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

파일 내용이 다음과 같다면,

Java
Spring
SQL

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

1: Java
2: Spring
3: SQL

15. 파일을 읽으면서 특정 단어 찾기

BufferedReader를 사용하면 파일을 한 줄씩 읽으면서 특정 단어가 포함된 줄만 찾을 수 있습니다.

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

public class Main {
    public static void main(String[] args) {
        String keyword = "Java";

        try (BufferedReader reader = new BufferedReader(new FileReader("memo.txt"))) {
            String line;

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

파일 안에 Java가 포함된 줄만 출력합니다.

16. 파일 내용을 ArrayList에 담기

파일을 읽은 뒤 각 줄을 ArrayList에 저장할 수도 있습니다.

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

public class Main {
    public static void main(String[] args) {
        ArrayList<String> lines = new ArrayList<>();

        try (BufferedReader reader = new BufferedReader(new FileReader("memo.txt"))) {
            String line;

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

        System.out.println(lines);
    }
}

파일의 각 줄이 리스트의 요소로 저장됩니다. 파일 데이터를 프로그램 안에서 다시 가공해야 할 때 사용할 수 있습니다.

17. ArrayList 내용을 파일로 저장하기

반대로 ArrayList에 들어 있는 값을 파일에 한 줄씩 저장할 수도 있습니다.

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

public class Main {
    public static void main(String[] args) {
        ArrayList<String> subjects = new ArrayList<>();

        subjects.add("Java");
        subjects.add("Spring");
        subjects.add("SQL");

        try (BufferedWriter writer = new BufferedWriter(new FileWriter("subjects.txt"))) {
            for (String subject : subjects) {
                writer.write(subject);
                writer.newLine();
            }

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

실행하면 subjects.txt에 다음처럼 저장됩니다.

Java
Spring
SQL

18. 기존 파일에 이어쓰기

기존 파일 내용 뒤에 새 내용을 추가하고 싶다면 FileWriter의 두 번째 인자로 true를 전달합니다.

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("memo.txt", true))) {
            writer.newLine();
            writer.write("추가로 저장한 내용입니다.");

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

new FileWriter("memo.txt", true)true는 기존 내용을 유지하고 뒤에 이어 쓰겠다는 의미입니다.

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

19. flush()란?

flush()는 버퍼에 남아 있는 데이터를 강제로 내보내는 메서드입니다.

BufferedWriter는 데이터를 바로 파일에 쓰지 않고 버퍼에 잠시 모아둘 수 있습니다. flush()를 호출하면 버퍼에 남아 있는 내용을 파일 쪽으로 밀어냅니다.

writer.write("Java");
writer.flush();

일반적으로 close()를 호출하면 내부적으로 flush가 함께 처리됩니다. 그래도 작업 중간에 반드시 파일에 반영해야 하는 상황이라면 flush()를 직접 호출할 수 있습니다.

기억하기
flush()는 버퍼에 남은 데이터를 내보내는 메서드이고, close()는 자원을 닫으면서 보통 flush까지 처리합니다.

20. close()와 try-with-resources

입출력 객체는 사용이 끝나면 닫아야 합니다. 직접 close()를 호출할 수도 있지만, 더 안전한 방법은 try-with-resources를 사용하는 것입니다.

직접 close() 호출

BufferedWriter writer = new BufferedWriter(new FileWriter("memo.txt"));

writer.write("Java");

writer.close();

try-with-resources 사용

try (BufferedWriter writer = new BufferedWriter(new FileWriter("memo.txt"))) {
    writer.write("Java");
}

try-with-resources를 사용하면 try 블록이 끝날 때 자동으로 자원이 닫힙니다. 예외가 발생해도 닫기 처리를 더 안전하게 맡길 수 있습니다.

21. try-with-resources로 읽기와 쓰기 함께 사용하기

파일을 복사하는 예제로 읽기와 쓰기를 함께 사용해보겠습니다.

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

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

            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }

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

source.txt를 한 줄씩 읽어서 copy.txt에 다시 씁니다. try 괄호 안에 여러 자원을 세미콜론으로 구분해서 작성할 수 있습니다.

22. 파일이 없을 때 발생할 수 있는 문제

BufferedReader로 파일을 읽을 때 해당 파일이 없으면 예외가 발생할 수 있습니다.

BufferedReader reader = new BufferedReader(new FileReader("missing.txt"));

missing.txt 파일이 없다면 파일을 열 수 없습니다. 그래서 읽기 작업 전에는 파일이 존재하는지 확인하거나, 예외 처리에서 상황에 맞는 메시지를 보여주는 것이 좋습니다.

import java.io.File;

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

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

23. 폴더가 없을 때 쓰기 오류가 날 수 있다

파일을 쓸 때 폴더가 없으면 오류가 발생할 수 있습니다.

BufferedWriter writer = new BufferedWriter(new FileWriter("data/memo.txt"));

위 코드는 data 폴더 안에 memo.txt를 만들겠다는 뜻입니다. 하지만 data 폴더가 없다면 파일을 만들 수 없어 오류가 발생할 수 있습니다.

필요하면 먼저 폴더를 만들어야 합니다.

import java.io.File;

File folder = new File("data");

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

24. 한글 깨짐 주의하기

텍스트 파일을 읽고 쓸 때 한글이 깨지는 경우가 있을 수 있습니다. 이는 파일의 문자 인코딩과 프로그램이 읽고 쓰는 방식이 맞지 않을 때 발생합니다.

FileReaderFileWriter는 기본 문자 인코딩을 사용합니다. 입문 단계에서는 우선 같은 환경에서 읽고 쓰는 연습을 하고, 인코딩을 명확히 지정해야 하는 경우에는 나중에 InputStreamReader, OutputStreamWriter, Files API 등을 함께 공부하면 좋습니다.

주의
한글이 깨진다면 파일 인코딩 문제일 수 있습니다. 파일을 저장한 인코딩과 읽는 인코딩이 맞는지 확인해야 합니다.

25. BufferedReader와 BufferedWriter 전체 예제

이번에는 공부 기록을 파일에 저장하고, 다시 읽어서 특정 단어가 포함된 줄만 출력하는 전체 예제를 보겠습니다.

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.mkdirs();
        }

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

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write("Java 파일 입출력 공부");
            writer.newLine();
            writer.write("BufferedWriter로 줄 단위 저장");
            writer.newLine();
            writer.write("BufferedReader로 줄 단위 읽기");
            writer.newLine();
            writer.write("Stream과 Optional도 복습하기");

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

        System.out.println("Buffered가 포함된 줄 찾기");

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

            while ((line = reader.readLine()) != null) {
                if (line.contains("Buffered")) {
                    System.out.println(lineNumber + ": " + line);
                }

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

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

공부 기록 저장 완료
Buffered가 포함된 줄 찾기
2: BufferedWriter로 줄 단위 저장
3: BufferedReader로 줄 단위 읽기

먼저 study 폴더를 만들고, java-study.txt 파일에 여러 줄을 저장했습니다. 그다음 파일을 한 줄씩 읽으면서 Buffered가 포함된 줄만 출력했습니다.

26. BufferedReader와 BufferedWriter 사용 시 자주 하는 실수

BufferedReader와 BufferedWriter를 처음 사용할 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
readLine() 종료 조건을 잘못 작성 마지막 줄 이후 처리에서 문제 발생 while ((line = reader.readLine()) != null) 형태 사용
write()만 쓰고 줄바꿈을 하지 않음 내용이 한 줄로 붙어서 저장됨 newLine() 사용
close()를 호출하지 않음 데이터가 완전히 저장되지 않거나 자원 누수 가능 try-with-resources 사용
이어쓰기 옵션을 모르고 파일을 덮어씀 기존 내용이 사라질 수 있음 new FileWriter(path, true) 사용
없는 폴더에 파일을 쓰려고 함 파일 생성 실패 가능 쓰기 전 mkdir() 또는 mkdirs()로 폴더 생성
BufferedReader가 줄바꿈 문자까지 읽는다고 생각 readLine()은 줄바꿈 문자를 포함하지 않음 다시 저장할 때 newLine() 직접 호출

27. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요. 메뉴 목록을 파일에 저장한 뒤 다시 읽어서 가격이 포함된 줄만 출력하는 예제입니다.

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

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

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

        String filePath = "data/menu.txt";

        ArrayList<String> menus = new ArrayList<>();

        menus.add("김밥,3500");
        menus.add("라면,4500");
        menus.add("돈가스,9000");

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            for (String menu : menus) {
                writer.write(menu);
                writer.newLine();
            }

            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) {
                if (line.contains(",")) {
                    String[] parts = line.split(",");

                    String name = parts[0];
                    String price = parts[1];

                    System.out.println(name + " 가격: " + price + "원");
                }
            }
        } catch (IOException e) {
            System.out.println("메뉴 읽기 중 오류가 발생했습니다.");
        }
    }
}

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

메뉴 저장 완료
저장된 메뉴 읽기
김밥 가격: 3500원
라면 가격: 4500원
돈가스 가격: 9000원

28. 이번 글 정리

이번 글에서는 텍스트 파일을 줄 단위로 읽고 쓰는 BufferedReaderBufferedWriter를 자세히 정리했습니다. 핵심 내용은 다음과 같습니다.

  • BufferedReader는 문자 데이터를 효율적으로 읽기 위한 클래스이다.
  • BufferedReaderreadLine()으로 파일을 한 줄씩 읽을 수 있다.
  • readLine()은 더 이상 읽을 줄이 없으면 null을 반환한다.
  • readLine()은 줄바꿈 문자를 포함하지 않는다.
  • BufferedWriter는 문자 데이터를 효율적으로 쓰기 위한 클래스이다.
  • BufferedWriterwrite()로 문자열을 쓰고, newLine()으로 줄바꿈을 추가한다.
  • BufferedReader는 보통 FileReader를 감싸서 사용한다.
  • BufferedWriter는 보통 FileWriter를 감싸서 사용한다.
  • FileWriter(path, true)를 사용하면 기존 파일에 이어쓸 수 있다.
  • flush()는 버퍼에 남은 데이터를 강제로 내보내는 메서드이다.
  • close()는 자원을 닫으며 보통 flush까지 함께 처리한다.
  • try-with-resources를 사용하면 입출력 객체를 자동으로 닫을 수 있다.
  • 파일을 읽기 전에는 파일 존재 여부를 확인하는 습관이 좋다.
  • 폴더 안에 파일을 쓰려면 먼저 폴더가 존재하는지 확인해야 한다.
  • 한글이 깨진다면 문자 인코딩 문제를 의심해야 한다.

한 줄 요약
BufferedReaderBufferedWriter는 텍스트 파일을 줄 단위로 읽고 쓰기 쉽게 해주는 클래스이며, 파일 입출력에서는 try-with-resources와 함께 사용하는 것이 안전합니다.

다음 글 예고
다음 글에서는 [Java 개념노트 63] 바이트 기반 입출력 이해하기라는 주제로 문자가 아닌 이미지, 파일, 바이너리 데이터를 다루는 InputStreamOutputStream의 기본 흐름을 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈