01. 기본 File I/O 이해
1. 파일 시스템(File System)
파일 시스템이란 데이터를 디렉터리와 파일 단위로 저장 및 관리하는 구조를 의미한다. 파일은 바이트(byte) 단위로 구성되며, 하드디스크를 비롯한 SSD 등의 물리 저장 장치에 위치해있다. Java에서는 이러한 파일 시스템을 File 클래스를 통해 추상화하여 다룰 수 있다.
| 절대 경로 (Absolute Path) |
상대 경로 (Relative Path) |
|
| 정의 | 루트 디렉토리부터 시작하는 전체 경로 | 현재 작업 디렉토리 기준으로 표시하는 경로 |
| 예시 | 윈도우 [C:\myWs\project\test-file.txt] 맥: [/home/user/project/test-file.txt] |
[./test-file.txt] | [../images/logo.png] |
File file = new File("data.txt");
System.out.println("파일 경로: " + file.getAbsolutePath());
System.out.println("파일 존재 여부: " + file.exists());
2. 입출력 개요
Java에서 입출력 데이터는 단방향 스트림을 통해 처리하며, 관련 API는 java.io 패키지 및 java.nio 패키지에서 제공한다.
| 입출력 스트림 종류 | |
| 입력 스트림 | 파일, 키보드, 네트워크 등에서 데이터를 읽음 |
| 출력 스트림 | 파일, 모니터, 네트워크 등으로 데이터를 씀 |
| java.io (Old IO) | java.nio (New IO) | |
| 입출력 방식 | 스트림 기반 (단방향) | 채널 기반 (양방향) |
| 버퍼 사용 | 논버퍼(Non-buffer) | 버퍼(Buffer) 사용 |
| 블로킹 여부 | 동기식 블로킹 방식 | 비동기 논블로킹 방식 |
| 적합한 곳 | 연결 수가 적고 데이터가 클 때 | 연결 수가 많고 데이터가 작을 때 |

3. 스트림 기반 입출력
스트림이란 데이터의 흐름을 추상화 한 개념으로, 연속적으로 데이터를 주고 받는 통로를 의미한다. 스트림은 크게 데이터 소스(파일, 네트워크, 메모리 등)에 직접 연결해서 데이터를 읽고 쓰는 기본 스트림과 기본 스트림에 연결되어 새로운 기능(성능 향상, 데이터 변환 등)을 추가하는 보조 스트림으로 나뉜다.
| 기본 스트림 (Base Stream) |
보조 스트림 (Filter Stream) |
|
| 직접 연결 여부 | 데이터 소스에 직접 연결 >> 파일, 네트워크 등 |
반드시 기본 스트림에 연결 |
| 객체 생성 | new FileInputStream("file.txt") | new BufferedInputStream(기본 스트림) |
| 주요 역할 | 실제 데이터 읽기 / 쓰기 | 속도 향상, 객체 직렬화, 변환 등 부가 기능 |
| 필수성 | 입출력을 위해 반드시 필요 | 선택 사항이지만 실무에선 거의 필수임 |
| 기본 스트림 | 입력 스트림 | 출력 스트림 |
| 바이트 기반 | InputStream | OutputStream |
| 문자 기반 | Reader | Writer |
| 보조 스트림 | 입력 스트림 | 출력 스트림 |
| 버퍼 처리 | BufferedInputStream | BufferedOutputStream |
| 문자 변환 | InputStreamReader | OutputStreamWriter |
| 기본형 처리 | DataInputStream | DataOutputStream |
| 객체 직렬화 | ObjectInputStream | ObjectOutputStream |
스트림 인스턴스를 생성할 때에는 반드시 try-catch 문을 통해 각종 IOException 계열의 예외를 처리해야 한다. 또한 장기간 실행 중인 프로그램에서 스트림을 닫지 않는 경우 다양한 리소스에서 누수(leak)가 발생하기 때문에, 스트림 인스턴스를 생성할 때에는 반드시 try-catch-finally 구문이나 try with resource 구문으로 자원을 반납해야 한다.
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
4. 파일 대상 데이터 읽기(FileInputStream) 실습
// 1. 사전 준비
echo code >> java.txt # test 파일 생성
// 2. 기본 스트림 예제
import java.io.FileInputStream;
public class FileInputStreamExample {
public static void main(String[] args) {
try {
FileInputStream fileInput = new FileInputStream("java.txt");
int i;
while ((i = fileInput.read()) != -1) {
System.out.print((char) i);
}
fileInput.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
// 3. 보조 스트림 사용 예제 (버퍼 단위 읽기)
import java.io.FileInputStream;
import java.io.BufferedInputStream;
public class FileInputStreamBuffered {
public static void main(String[] args) {
try {
FileInputStream fileInput = new FileInputStream("java.txt");
BufferedInputStream bufferedInput = new BufferedInputStream(fileInput);
int i;
while ((i = bufferedInput.read()) != -1) {
System.out.print((char) i);
}
bufferedInput.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
5. 파일 대상 쓰기(FileOutputStream) 실습
// 1. 기본 스트림 예제: 기존 파일이 있으면 덮어쓰고, 없으면 새로 생성
import java.io.FileOutputStream;
public class FileOutputStreamExample {
public static void main(String[] args) {
try {
FileOutputStream fileOutput = new FileOutputStream("java.txt");
String word = "ja";
byte[] b = word.getBytes();
fileOutput.write(b);
fileOutput.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
02. 자료구조 데이터의 영속화
1. 문자 기반 스트림
Java에서는 2 byte 크기를 갖는 char 타입의 데이터를 읽고 쓰기 위해 문자 기반 스트림인 FileReader와 FileWriter를 제공한다. 이러한 문자 기반 스트림과 그 하위 클래스는 여러 종류의 인코딩(Encoding)과 자바에서 사용하는 유니코드(UTF-16) 간의 변환을 자동으로 처리한다.
2. 데이터 영속화 예제 | List 데이터
// 1. txt 파일로 저장
List<String> names = List.of("Alice", "Bob", "Charlie");
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter("names.txt"));
for (String name : names) {
writer.write(name);
writer.newLine(); // 줄 단위로 파일 기록
}
writer.flush(); // 강제 저장
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close(); // 파일 닫기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 2. txt 파일 -> List 데이터 로드
Set<String> uniqueWords = new HashSet<>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("words.txt"));
String line;
while ((line = reader.readLine()) != null) {
uniqueWords.add(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 데이터 영속화 예제 | Set 데이터
// 1. txt 파일 저장
Set<String> uniqueWords = Set.of("apple", "banana", "cherry");
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter("words.txt"));
for (String word : uniqueWords) {
writer.write(word);
writer.newLine(); // 줄 단위 저장
}
writer.flush(); // 강제 저장
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close(); // 파일 닫기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 2. txt 파일 -> set 데이터 로드
Set<String> uniqueWords = new HashSet<>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("words.txt"));
String line;
while ((line = reader.readLine()) != null) {
uniqueWords.add(line); // 중복 제거
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 데이터 영속화 예제 | Map 데이터
// 1. txt 파일 저장
Map<String, Integer> scoreMap = Map.of("Tom", 90, "Jane", 85, "Paul", 95);
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter("scores.txt"));
for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
writer.write(entry.getKey() + ":" + entry.getValue()); // : 구분자로 파일 저장
writer.newLine();
}
writer.flush(); // 강제 저장
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close(); // 파일 닫기
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 2. txt 파일을 Map으로 로드
Map<String, Integer> scoreMap = new HashMap<>();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("scores.txt"));
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(":"); // : 기준으로 문자열 분리
if (parts.length == 2) {
scoreMap.put(parts[0], Integer.parseInt(parts[1]));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
03. 실습 | CVS 파일 CRUD 작업
1. CSV 파일
CSV 파일이란 엑셀, 메모장, 구글 스프레드시트 등 다양한 환경에서 가볍게 데이터 관리가 가능한 텍스트 기반 포맷으로, 각 필드를 쉼표(,)로 구분하고 각 행을 줄바꿈 문자(\r\n 혹은 \n)로 구분한다. 특히 OS 별로 구분되는 줄바꿈 문자는 System.LineSeparator()로 통일성 있게 처리 가능하다. 이러한 CSV 파일을 읽을 때는 헤더를 생략하고, 쓸 때는 첫 줄에 반드시 헤더를 포함해야 한다.
name,age,email
Alice,25,alice@ohgiraffers.com
Bob,30,bob@ohgiraffers.com
Sam,18,samuel@ohgiraffers.com
2. 기본 데이터 클래스 | Member 클래스
public class Member {
private String name;
private int age;
private String email;
public Member(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
// toCSV: CSV 파일에 쓸 수 있도록 쉼표로 각 필드를 연결
public String toCSV() {
return name + "," + age + "," + email;
}
}
3. 파일 입출력 클래스 | MemberRepository 클래스
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class MemberRepository {
private static final String FILE_PATH = "data\members.csv";
private static final String LINE_SEPARATOR = System.lineSeparator();
// findAll(): 모든 데이터를 메모리로 불러오는 함수
public List<Member> findAll() {
List<Member> list = new ArrayList<>();
// try with resource 구문으로 작성
try (BufferedReader br = new BufferedReader(new FileReader(FILE_PATH))) {
// header는 스킵
String line = br.readLine();
while ((line = br.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length == 3) {
list.add(new Member(parts[0], Integer.parseInt(parts[1]), parts[2]));
}
}
} catch (IOException e) {
e.printStackTrace();
}
return list;
}
// saveAll(): 리스트를 전체 파일로 덮어쓰는 함수
public void saveAll(List<Member> members) {
// try with resource 구문으로 작성
try (BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_PATH))) {
bw.write("name,age,email" + LINE_SEPARATOR);
for (Member m : members) {
bw.write(m.toCSV() + LINE_SEPARATOR);
}
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 비지니스 클래스 | MemberService 클래스
import java.util.List;
public class MemberService {
private final MemberRepository repository;
// 생성자 주입
public MemberService(MemberRepository repository) {
this.repository = repository;
}
// 조회(read)
public List<Member> getAllMembers() {
return repository.findAll();
}
// 추가(create)
public void registerMember(Member member) {
List<Member> members = repository.findAll();
members.add(member);
repository.saveAll(members);
}
// 수정(update)
public void updateMember(String email, String newName, int newAge) {
List<Member> members = repository.findAll();
for (Member m : members) {
if (m.getEmail().equals(email)) {
m.setName(newName);
m.setAge(newAge);
break;
}
}
repository.saveAll(members);
}
// 삭제(delete)
public void deleteMember(String email) {
List<Member> members = repository.findAll();
members.removeIf(m -> m.getEmail().equals(email));
repository.saveAll(members);
}
}
5. 사용자 인터페이스 클래스 | Application 클래스
import java.util.Scanner;
public class Application {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
MemberRepository repository = new MemberRepository();
MemberService service = new MemberService(repository);
while (true) {
System.out.println("\n---------- [회원 관리 시스템] ----------");
System.out.println("1. 회원 등록 | 2. 전체 조회 | 3. 수정 | 4. 삭제 | 9. 종료");
System.out.print("선택: ");
String input = scanner.nextLine();
switch (input) {
case "1":
System.out.print("이름: ");
String name = scanner.nextLine();
System.out.print("나이: ");
int age = Integer.parseInt(scanner.nextLine());
System.out.print("이메일: ");
String email = scanner.nextLine();
service.registerMember(new Member(name, age, email));
break;
case "2":
System.out.println("[전체 회원 목록]");
for (Member m : service.getAllMembers()) {
System.out.println(m.toCSV());
}
break;
case "3":
System.out.print("수정할 이메일 입력: ");
String targetEmail = scanner.nextLine();
System.out.print("새 이름: ");
String newName = scanner.nextLine();
System.out.print("새 나이: ");
int newAge = Integer.parseInt(scanner.nextLine());
service.updateMember(targetEmail, newName, newAge);
break;
case "4":
System.out.print("삭제할 이메일 입력: ");
String deleteEmail = scanner.nextLine();
service.deleteMember(deleteEmail);
break;
case "9":
System.out.println("프로그램을 종료합니다.");
return;
default:
System.out.println("잘못된 입력입니다.");
}
}
}
}
'[Codeit Review] > 알고리즘과 자료구조' 카테고리의 다른 글
| [05] 객체 직렬화 (0) | 2026.01.30 |
|---|---|
| [03] 제네릭 타입과 메서드 (0) | 2026.01.30 |
| [02] 주요 자료 구조 (0) | 2026.01.27 |