10편. Python 파일 입출력 – Java IO vs Python open() 비교
📚 목차
1. 텍스트 파일 입출력 - Java 스트림 vs Python open()
2. 버퍼 IO 처리 - 성능을 위한 구조적 접근
3. 텍스트 인코딩과 예외 처리 전략
4. 바이너리 IO - 원시 바이트와 객체 직렬화 비교
5. 포맷별 입출력 - JSON, CSV, YAML 처리 방식
✔ 마무리 - IO 전략 선택의 기준
Java 개발자에게 파일 입출력(IO)은 친숙한 영역입니다.
FileReader, BufferedWriter, FileInputStream, ObjectOutputStream 같은 클래스 조합을 통해 다양한 형식의 데이터를 읽고 쓰는 작업에 익숙할 것입니다.
하지만 Python에서는 전혀 다른 접근이 펼쳐집니다. 복잡한 스트림 계층 대신 open() 함수 하나로 텍스트/바이너리 입출력을 모두 처리할 수 있으며, with 문으로 자원 관리까지 간결하게 할 수 있습니다.
여기에 pathlib, pickle, csv, json 같은 내장 모듈을 활용하면 실무에서 사용하는 다양한 데이터 입출력 처리를 단 몇 줄의 코드로 구현할 수 있습니다.
이 글에서는 다섯 가지 관점에서 Java와 Python의 파일 입출력 구조를 비교합니다:

1. 텍스트 파일 입출력 – Java 스트림 vs Python open()
텍스트 파일은 가장 일반적인 파일 형식으로, 사람이 읽을 수 있는 문자열 데이터를 포함합니다. Java와 Python은 이 텍스트 파일을 다루는 방식에서 철학적인 차이를 보입니다.
🔷 Java 방식: 계층적 문자 스트림
Java에서 텍스트 파일 입출력의 기본은 FileReader와 FileWriter입니다. 이들은 문자 스트림을 처리하며, 플랫폼의 기본 문자 인코딩을 사용합니다.
대용량 파일이나 효율적인 라인 단위 처리를 위해서는 BufferedReader와 BufferedWriter와 같은 버퍼링 래퍼 클래스를 사용하는 것이 일반적입니다.
✔️ 주요 클래스:
▪️FileReader: 파일에서 문자를 읽어들이는 스트림.
▪️FileWriter: 파일에 문자를 쓰는 스트림.
▪️BufferedReader: FileReader와 같은 다른 문자 입력 스트림에 버퍼링 기능을 추가하여 효율적인 라인 단위 읽기를 가능하게 합니다. readLine() 메서드가 핵심입니다.
▪️BufferedWriter: FileWriter와 같은 다른 문자 출력 스트림에 버퍼링 기능을 추가하여 효율적인 문자 쓰기를 가능하게 합니다.
newLine() 메서드로 플랫폼 독립적인 줄 바꿈을 할 수 있습니다.
✔️ 자원 관리 (try-with-resources):
Java 7부터 도입된 try-with-resources 문은 AutoCloseable 인터페이스를 구현하는 객체(예: 파일 스트림)의 자원 관리를 매우 편리하게 해줍니다. try 블록이 종료되면 자동으로 자원이 닫히므로 close()를 명시적으로 호출할 필요가 없습니다.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class TextIOJavaExample {
public static void main(String[] args) {
String filename = "java_hello.txt";
String content = "Hello from Java!\nThis is a second line.\nAnd a third.";
// 파일 쓰기 (FileWriter, BufferedWriter 조합 및 try-with-resources)
// FileWriter는 문자 단위, BufferedWriter는 버퍼링을 통해 쓰기 성능 향상
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(content);
System.out.println("Java: '" + filename + "'에 내용 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: 파일 쓰기 오류 발생: " + e.getMessage());
}
// 파일 읽기 (FileReader, BufferedReader 조합 및 try-with-resources)
// FileReader는 문자 단위, BufferedReader는 버퍼링 및 라인 단위 읽기 기능 제공
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
System.out.println("\nJava: '" + filename + "'에서 내용 읽기:");
String line;
while ((line = reader.readLine()) != null) { // 라인 단위로 읽기
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Java: 파일 읽기 오류 발생: " + e.getMessage());
}
}
}
🔷 Python 방식: 단일 진입점 open()
Python에서 파일 입출력의 핵심은 내장 함수 open()입니다.
이 함수는 파일 객체(file object)를 반환하며, 이 객체를 통해 읽고 쓰는 작업을 수행합니다.
Python 3에서는 문자열 처리에 유니코드를 기본으로 사용하며, encoding 매개변수를 통해 인코딩을 명시하는 것이 중요합니다.
pathlib 모듈은 객체 지향적인 방식으로 파일 시스템 경로를 다루며, 간결한 파일 I/O 메서드를 제공합니다.
✔️ 주요 기능 및 모듈:
▪️open():
- 파일을 열고 파일 객체를 반환합니다.
- mode: 'r' (읽기), 'w' (쓰기, 파일이 없으면 생성, 있으면 덮어쓰기), 'a' (이어쓰기), 'x' (배타적 생성), 'b' (바이너리 모드), '+' (읽기/쓰기).
- encoding: 텍스트 모드에서 사용할 문자 인코딩(예: 'utf-8').
▪️파일 객체 메서드:
- read(): 파일 전체 내용을 문자열로 읽습니다.
- readline(): 한 줄을 읽습니다.
- readlines(): 모든 라인을 리스트로 읽습니다.
- write(str): 문자열을 파일에 씁니다.
- writelines(list_of_strings): 문자열 리스트를 파일에 씁니다.
▪️with 문:
- Python의 with 문은 자원 관리를 위한 컨텍스트 매니저를 사용합니다.
- open() 함수와 함께 사용하면 with 블록을 벗어날 때 파일이 자동으로 닫힙니다. Java의 try-with-resources와 유사합니다.
▪️pathlib.Path:
- Python 3.4부터 표준 라이브러리에 포함된 모듈로, 파일 시스템 경로를 객체로 다룹니다.
- Path('filename').write_text(content, encoding='utf-8'): 파일에 텍스트를 한 번에 씁니다.
- Path('filename').read_text(encoding='utf-8'): 파일에서 텍스트를 한 번에 읽습니다.
import os
from pathlib import Path
filename_open = "python_hello_open.txt"
filename_pathlib = "python_hello_pathlib.txt"
content = "Hello from Python!\nThis is a second line.\nAnd a third."
# 1. open() 함수를 사용하여 텍스트 파일 쓰기/읽기
# 'w' 모드는 파일을 쓰기 모드로 열고, 파일이 이미 존재하면 내용을 지우고 새로 씁니다.
# 'encoding="utf-8"'은 유니코드 문자를 올바르게 처리하기 위해 중요합니다.
with open(filename_open, "w", encoding="utf-8") as f:
f.write(content)
print(f"Python (open()): '{filename_open}'에 내용 쓰기 완료.")
# 'r' 모드는 파일을 읽기 모드로 엽니다.
with open(filename_open, "r", encoding="utf-8") as f:
print(f"\nPython (open()): '{filename_open}'에서 내용 읽기:")
# 파일 객체는 이터러블하여 라인 단위로 순회할 수 있습니다.
for line in f:
print(line.strip()) # strip()으로 각 라인 끝의 줄바꿈 문자 제거
# 2. pathlib.Path를 사용하여 텍스트 파일 쓰기/읽기
# write_text() 메서드는 한 번에 모든 텍스트를 파일에 씁니다.
Path(filename_pathlib).write_text(content, encoding="utf-8")
print(f"\nPython (pathlib): '{filename_pathlib}'에 내용 쓰기 완료.")
# read_text() 메서드는 한 번에 모든 텍스트를 파일에서 읽습니다.
print(f"\nPython (pathlib): '{filename_pathlib}'에서 내용 읽기:")
print(Path(filename_pathlib).read_text(encoding="utf-8"))
# 생성된 파일 정리 (선택 사항)
# os.remove(filename_open)
# os.remove(filename_pathlib)
🔷 Java vs Python 일반 텍스트 IO 비교
| 항목 | Java (FileReader, FileWriter, BufferedReader) | Python (open(), pathlib.Path) |
| 파일 열기 | FileReader, FileWriter | open() 함수 |
| 라인 단위 읽기 | BufferedReader.readLine() | 파일 객체를 직접 이터레이션 (for line in f), readline() 사용 가능 |
| 전체 읽기/쓰기 | 지원 안됨 (라인 기반 또는 버퍼링된 문자 스트림) | read(), write(), 또는 read_text(), write_text() (pathlib 사용) |
| 문자 인코딩 | 명시적 지정 가능 (예: new InputStreamReader(new FileInputStream(...), "UTF-8")) 또는 플랫폼 기본 인코딩 사용 | open()의 encoding 매개변수로 지정 (예: encoding="utf-8" – 권장) |
| 자원 관리 | try-with-resources 문법 사용 시 자동으로 자원 닫힘 | with 문 사용 시 자동으로 파일 닫힘 |
| 코드 간결성 | 여러 클래스 조합 필요 → 다소 장황함 | open() 또는 pathlib를 사용해 매우 간단하고 직관적 |
Java는 여러 클래스를 조합하여 처리해야 하는 반면, Python은 open() 함수 하나로 파일 열기부터 읽기, 쓰기, 자원 관리까지 매우 간결하게 처리할 수 있는 점이 두드러집니다.
특히 pathlib의 read_text() / write_text() 메서드는 전체 파일 내용을 다루는 작업을 더욱 단순화해 줍니다.
2. 버퍼 IO 처리 – 성능을 위한 구조적 접근
버퍼 입출력은 작은 데이터 조각을 한 번에 처리하는 대신, 일정량의 데이터를 모아 한 번에 읽거나 쓰는 방식으로 파일 I/O 성능을 향상시킵니다. 대용량 파일을 다룰 때 특히 중요합니다.
✔️ Java: BufferedReader / BufferedWriter
Java에서는 BufferedReader와 BufferedWriter가 문자 스트림의 버퍼링을 담당합니다. readLine()과 write() 메서드가 내부적으로 버퍼를 사용하여 효율적인 작업을 수행합니다.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferIOJavaExample {
public static void main(String[] args) {
String filename = "java_buffer.txt";
String content = "Buffered write example for Java. This line is written using a buffer.";
// BufferedWriter를 이용한 파일 쓰기
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(content);
writer.newLine(); // 플랫폼 독립적인 줄바꿈
writer.write("Another line using BufferedWriter.");
System.out.println("Java: '" + filename + "'에 버퍼 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: 버퍼 쓰기 오류 발생: " + e.getMessage());
}
// BufferedReader를 이용한 파일 읽기
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
System.out.println("\nJava: '" + filename + "'에서 버퍼 읽기:");
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Java: 버퍼 읽기 오류 발생: " + e.getMessage());
}
}
}
✔️ Python: io.BufferedReader / io.BufferedWriter
Python의 io 모듈은 파일 I/O를 위한 추상 베이스 클래스와 다양한 구현체를 제공합니다.
io.BufferedReader와 io.BufferedWriter는 바이너리 모드 파일 객체에 버퍼링 기능을 추가합니다.
텍스트 데이터를 처리할 때는 decode()나 encode()를 사용하여 바이트와 문자열 간 변환이 필요합니다.
import io
import os
filename = "python_buffer.txt"
content = b"Buffered write example for Python.\nThis is written as bytes."
# io.BufferedWriter를 이용한 파일 쓰기 (바이너리 모드)
# raw 스트림을 'wb'로 열고, 그 위에 BufferedWriter를 래핑합니다.
with open(filename, "wb") as raw_file:
writer = io.BufferedWriter(raw_file)
writer.write(content)
writer.flush() # 버퍼의 내용을 즉시 파일에 씁니다.
print(f"Python: '{filename}'에 버퍼 쓰기 완료.")
# io.BufferedReader를 이용한 파일 읽기 (바이너리 모드)
# raw 스트림을 'rb'로 열고, 그 위에 BufferedReader를 래핑합니다.
with open(filename, "rb") as raw_file:
reader = io.BufferedReader(raw_file)
read_data = reader.read() # 모든 바이트를 읽음
print(f"\nPython: '{filename}'에서 버퍼 읽기:")
print(read_data.decode("utf-8")) # 바이트를 utf-8로 디코딩하여 문자열로 출력
# 생성된 파일 정리 (선택 사항)
# os.remove(filename)
✔️ Java vs Python 버퍼 입출력 비교
| 항목 | Java | Python |
| 클래스 명칭 | BufferedReader, BufferedWriter | io.BufferedReader, io.BufferedWriter |
| 사용 방식 | 문자 단위 (기본적으로 텍스트 스트림과 결합하여 사용) | 바이트 기반 (raw 바이너리 스트림 위에 래핑, 텍스트는 인코딩/디코딩 필요) |
| 특징 | 자동 버퍼링 제공, readLine()으로 줄 단위 읽기 가능 | raw 파일 객체 + 버퍼 객체의 조합으로 유연한 사용, flush()로 강제 쓰기 가능 |
Java는 텍스트 스트림 중심의 버퍼링에 적합한 구조이고, Python은 바이트 스트림 기반의 유연한 조합 방식을 채택합니다.
특히 Python에서는 io 모듈을 활용해 다양한 계층 구조의 파일 처리를 구성할 수 있으며, flush() 메서드를 통해 버퍼 내용을 수동으로 조정할 수 있는 점이 특징입니다.
3. 텍스트 인코딩과 예외 처리 전략
파일 입출력에서 텍스트 인코딩은 매우 중요합니다. 잘못된 인코딩으로 파일을 읽거나 쓰면 문자가 깨지는 현상(문자셋 깨짐, "Mojibake")이 발생할 수 있습니다.
또한 파일이 존재하지 않거나 접근 권한이 없는 경우 등 다양한 에러 상황에 대한 처리는 안정적인 애플리케이션 개발에 필수적입니다.

✔️Java: 명시적 인코딩과 IOException
Java에서는 InputStreamReader와 OutputStreamWriter를 FileInputStream 및 FileOutputStream과 조합하여 명시적으로 문자 인코딩을 지정할 수 있습니다.
모든 I/O 작업은 IOException을 발생시킬 수 있으므로 try-catch 블록으로 예외 처리를 해야 합니다.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.FileNotFoundException;
public class EncodingErrorHandlingJavaExample {
public static void main(String[] args) {
String filename = "java_encoded.txt";
String content = "안녕하세요, 자바입니다! Hello from Java!";
String encoding = "UTF-8"; // 명시적으로 UTF-8 인코딩 지정
// 1. 명시적 인코딩으로 파일 쓰기
// OutputStreamWriter를 통해 FileOutputStream에 인코딩을 적용
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(filename), encoding))) {
writer.write(content);
System.out.println("Java: '" + filename + "'에 " + encoding + "로 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: 파일 쓰기 중 오류 발생: " + e.getMessage());
}
// 2. 명시적 인코딩으로 파일 읽기
// InputStreamReader를 통해 FileInputStream에 인코딩을 적용
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(filename), encoding))) {
String line;
System.out.println("\nJava: '" + filename + "'에서 " + encoding + "로 읽기:");
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.err.println("Java: 파일을 찾을 수 없습니다: " + e.getMessage());
} catch (IOException e) {
System.err.println("Java: 파일 읽기 중 오류 발생: " + e.getMessage());
}
// 3. 존재하지 않는 파일 읽기 시도 (예외 처리 예시)
String nonExistentFile = "non_existent.txt";
try (BufferedReader reader = new BufferedReader(new FileReader(nonExistentFile))) {
String line = reader.readLine();
System.out.println(line);
} catch (FileNotFoundException e) {
System.err.println("\nJava: " + nonExistentFile + " 읽기 시도 - 파일이 없습니다 (예외 발생): " + e.getMessage());
} catch (IOException e) {
System.err.println("Java: 기타 I/O 오류 발생: " + e.getMessage());
}
}
}
✔️ Python: encoding과 errors 매개변수
Python의 open() 함수는 encoding 매개변수를 통해 텍스트 인코딩을 쉽게 지정할 수 있습니다. 또한 errors 매개변수를 사용하여 인코딩/디코딩 에러 발생 시의 동작을 제어할 수 있습니다. 파일 I/O 관련 에러는 try-except 블록으로 처리합니다.
import os
from pathlib import Path
text_filename = "python_encoded.txt"
error_filename = "non_utf8.txt"
non_existent_filename = "not_here.txt"
content_korean = "안녕하세요, 파이썬입니다! Hello from Python!"
content_invalid_ascii = "안녕".encode('euc-kr') # 강제로 EUC-KR로 인코딩된 바이트
# 1. UTF-8로 파일 쓰기 (기본 및 권장)
try:
with open(text_filename, "w", encoding="utf-8") as f:
f.write(content_korean)
print(f"Python: '{text_filename}'에 UTF-8로 쓰기 완료.")
except IOError as e:
print(f"Python: 파일 쓰기 오류 발생: {e}")
# 2. UTF-8로 파일 읽기
try:
with open(text_filename, "r", encoding="utf-8") as f:
read_content = f.read()
print(f"\nPython: '{text_filename}'에서 UTF-8로 읽기:")
print(read_content)
except FileNotFoundError:
print(f"Python: '{text_filename}' 파일을 찾을 수 없습니다.")
except UnicodeDecodeError as e:
print(f"Python: '{text_filename}' 디코딩 오류: {e}")
except IOError as e:
print(f"Python: 파일 읽기 중 기타 오류 발생: {e}")
# 3. 잘못된 인코딩으로 읽기 시도 및 에러 처리 (errors 매개변수)
# EUC-KR로 인코딩된 파일이 있다고 가정하고 UTF-8로 읽으면 오류 발생
try:
# 먼저 EUC-KR로 파일을 생성 (테스트용)
with open(error_filename, "wb") as f:
f.write(content_invalid_ascii)
print(f"\nPython: '{error_filename}' (EUC-KR 인코딩) 생성 완료.")
# 잘못된 인코딩 (utf-8)으로 읽기 시도
with open(error_filename, "r", encoding="utf-8") as f:
bad_read = f.read()
print(f"Python (잘못된 인코딩): '{error_filename}'에서 읽은 내용: {bad_read}")
except UnicodeDecodeError as e:
print(f"Python (에러 처리): '{error_filename}' 디코딩 오류 발생: {e}")
# errors='ignore'로 오류 무시
with open(error_filename, "r", encoding="utf-8", errors='ignore') as f:
ignored_read = f.read()
print(f"Python (errors='ignore'): '{error_filename}'에서 오류 무시하고 읽은 내용: {ignored_read}")
# errors='replace'로 오류 문자 대체
with open(error_filename, "r", encoding="utf-8", errors='replace') as f:
replaced_read = f.read()
print(f"Python (errors='replace'): '{error_filename}'에서 오류 문자 대체하고 읽은 내용: {replaced_read}")
except FileNotFoundError:
print(f"Python: '{error_filename}' 파일을 찾을 수 없습니다.")
# 4. 존재하지 않는 파일 읽기 시도 (예외 처리 예시)
try:
with open(non_existent_filename, "r") as f:
f.read()
except FileNotFoundError:
print(f"\nPython: '{non_existent_filename}' 읽기 시도 - 파일을 찾을 수 없습니다 (예외 발생).")
except Exception as e:
print(f"\nPython: 기타 오류 발생: {e}")
# 생성된 파일 정리 (선택 사항)
# os.remove(text_filename)
# os.remove(error_filename)
✔️ Java vs Python 인코딩 및 에러 처리 비교
| 비교 | Java | Python |
| 인코딩 지정 | InputStreamReader, OutputStreamWriter에 인코딩 문자열 전달 | open() 함수의 encoding 매개변수 사용 |
| 에러 발생 시 처리 | IOException 발생 → try-catch 문으로 처리 | UnicodeEncodeError, UnicodeDecodeError 발생 → errors 매개변수로 동작 제어 가능 |
| 파일 없음 에러 | FileNotFoundException | FileNotFoundError |
| 일반 I/O 에러 | IOException | IOError (Python 3에서는 OSError의 하위 유형으로 세분화됨) |
| 추천 인코딩 | UTF-8 | UTF-8 |
Java에서는 인코딩을 다룰 때 InputStreamReader나 OutputStreamWriter에 문자열로 지정하고, 모든 I/O 에러는 IOException으로 포괄 처리됩니다.
반면 Python은 open() 함수에서 encoding, errors 매개변수를 통해 더 세분화된 인코딩 제어와 예외 대응이 가능하며, UnicodeDecodeError나 FileNotFoundError 같은 구체적인 예외로 원인 파악이 용이합니다.
4. 바이너리 IO – 원시 바이트와 객체 직렬화 비교
바이너리 파일은 텍스트가 아닌 원시 바이트 데이터를 포함합니다. 이미지, 오디오, 비디오 파일, 실행 파일, 또는 프로그램 내부 객체를 직렬화한 파일 등이 바이너리 파일에 해당합니다. 바이너리 IO는 데이터의 손실 없이 원시 바이트를 있는 그대로 처리해야 할 때 사용됩니다.
🔷 Java: FileInputStream, FileOutputStream, DataInputStream, DataOutputStream
Java의 바이너리 IO는 InputStream과 OutputStream 계열의 클래스를 사용합니다. 바이트 스트림을 직접 다루며, 이는 어떤 종류의 데이터든 처리할 수 있다는 유연성을 제공합니다.
✔️ 주요 클래스:
▪️ FileInputStream: 파일에서 바이트 데이터를 읽어들이는 스트림.
▪️ FileOutputStream: 파일에 바이트 데이터를 쓰는 스트림.
▪️ DataInputStream, DataOutputStream: 원시 자바 데이터 타입(int, double, boolean 등)을 바이트 스트림으로 읽고 쓸 수 있는 기능을 제공합니다. 이는 구조화된 바이너리 데이터를 다룰 때 유용합니다.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class BinaryIOJavaExample {
public static void main(String[] args) {
String filenameRaw = "java_binary_raw.dat";
String filenameData = "java_binary_data.dat";
// 1. 원시 바이트 쓰기/읽기 (FileInputStream, FileOutputStream)
byte[] rawBytes = {1, 2, 3, 127, -128, (byte) 255}; // -128은 10000000, 255는 11111111 (unsigned)
try (FileOutputStream out = new FileOutputStream(filenameRaw)) {
out.write(rawBytes); // 바이트 배열 통째로 쓰기
System.out.println("Java: '" + filenameRaw + "'에 원시 바이트 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: 원시 바이트 쓰기 오류: " + e.getMessage());
}
try (FileInputStream in = new FileInputStream(filenameRaw)) {
System.out.println("\nJava: '" + filenameRaw + "'에서 원시 바이트 읽기:");
int b;
while ((b = in.read()) != -1) { // 1바이트씩 읽기 (int로 반환되므로 -1이 EOF)
System.out.print(b + " "); // 읽은 바이트는 0-255 범위의 int로 변환
}
System.out.println();
} catch (IOException e) {
System.err.println("Java: 원시 바이트 읽기 오류: " + e.getMessage());
}
// 2. 구조화된 데이터 쓰기/읽기 (DataInputStream, DataOutputStream)
int intValue = 12345;
double doubleValue = 3.14159;
boolean booleanValue = true;
try (DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(filenameData))) {
dataOut.writeInt(intValue); // int 쓰기 (4바이트)
dataOut.writeDouble(doubleValue); // double 쓰기 (8바이트)
dataOut.writeBoolean(booleanValue); // boolean 쓰기 (1바이트)
System.out.println("\nJava: '" + filenameData + "'에 구조화된 데이터 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: 구조화된 데이터 쓰기 오류: " + e.getMessage());
}
try (DataInputStream dataIn = new DataInputStream(new FileInputStream(filenameData))) {
System.out.println("\nJava: '" + filenameData + "'에서 구조화된 데이터 읽기:");
System.out.println("읽은 int: " + dataIn.readInt());
System.out.println("읽은 double: " + dataIn.readDouble());
System.out.println("읽은 boolean: " + dataIn.readBoolean());
} catch (IOException e) {
System.err.println("Java: 구조화된 데이터 읽기 오류: " + e.getMessage());
}
}
}
🔷 Python: open('rb'), io.BytesIO, pickle
Python에서 바이너리 파일 입출력은 open() 함수의 모드를 'b'로 지정하여 수행합니다. 텍스트 모드와 달리 encoding 매개변수는 사용하지 않습니다. 바이너리 모드에서는 bytes 타입의 데이터를 읽고 씁니다.
pickle 모듈은 Python 객체를 직렬화(serialization)하여 파일로 저장하고 다시 로드하는 데 사용됩니다.
✔️ 주요 기능 및 모듈:
▪️open(filename, mode='rb'/'wb'): 파일을 바이너리 읽기/쓰기 모드로 엽니다.
▪️bytes 객체: Python에서 불변(immutable) 바이트 시퀀스를 나타냅니다. 바이너리 데이터를 처리할 때 사용합니다.
- b'hello' (바이트 리터럴), bytes([1, 2, 3]), str.encode('utf-8') 등으로 생성.
▪️파일 객체 메서드:
- read(size): size 바이트를 읽습니다. size를 생략하면 파일 전체를 읽습니다. bytes 객체를 반환합니다.
- write(bytes_object): bytes 객체를 파일에 씁니다.
▪️io.BytesIO: 메모리 내에서 바이너리 데이터를 파일처럼 다룰 수 있게 해줍니다. 실제 파일 시스템에 접근하지 않고 바이트 스트림을 생성, 읽기, 쓰기 할 때 유용합니다.
▪️pickle: Python 객체 구조를 바이트 스트림으로 변환(직렬화/피클링)하고, 다시 객체로 복원(역직렬화/언피클링)하는 표준 모듈입니다.
import pickle
import io
import os
# 1. 일반 바이너리 쓰기/읽기 (open() with 'wb'/'rb' mode)
binary_filename = "python_binary.dat"
# 바이트 데이터: 0부터 255까지의 정수 (b'\x01\x02\x03\x7f\x80\xff')
# Python의 byte는 0-255 범위의 부호 없는 정수로 표현됩니다.
data_to_write = bytes([1, 2, 3, 127, 128, 255]) # 128은 0x80, 255는 0xff
with open(binary_filename, "wb") as f: # 'wb' 모드: 바이너리 쓰기
f.write(data_to_write)
print(f"Python: '{binary_filename}'에 바이너리 데이터 쓰기 완료.")
with open(binary_filename, "rb") as f: # 'rb' 모드: 바이너리 읽기
read_data = f.read() # 파일 전체를 바이트로 읽기
print(f"\nPython: '{binary_filename}'에서 바이너리 데이터 읽기:")
print(f"읽은 바이트: {list(read_data)}") # 바이트를 정수 리스트로 변환하여 출력
# 2. 객체 직렬화 예시 (pickle)
obj_filename = "python_object.pkl"
my_data = {"name": "Python", "version": 3.10, "is_awesome": True, "data_list": [1, 2, 3]}
with open(obj_filename, "wb") as f:
pickle.dump(my_data, f) # 객체를 파일에 직렬화하여 씀
print(f"\nPython: '{obj_filename}'에 객체 직렬화 완료.")
with open(obj_filename, "rb") as f:
loaded_data = pickle.load(f) # 파일에서 객체를 역직렬화하여 로드
print(f"\nPython: '{obj_filename}'에서 객체 역직렬화 완료:")
print(f"로드된 데이터: {loaded_data}")
# 3. 메모리 기반 바이너리 IO (io.BytesIO)
# 실제 파일 없이 메모리에서 바이트 스트림을 다룰 때 유용
byte_stream = io.BytesIO()
byte_stream.write(b"Hello, BytesIO!") # 바이트 쓰기
byte_stream.write(bytes([10, 20, 30]))
byte_stream.seek(0) # 스트림의 시작으로 포인터 이동
read_from_memory = byte_stream.read()
print(f"\nPython (BytesIO): 메모리 스트림에서 읽은 데이터: {read_from_memory}")
# 생성된 파일 정리 (선택 사항)
# os.remove(binary_filename)
# os.remove(obj_filename)
🔷 Java vs Python 바이너리 IO 비교
| 항목 | Java | Python |
| 일반 바이너리 IO | FileInputStream, FileOutputStream | open(mode="rb"), open(mode="wb") |
| 원시 데이터 타입 IO | DataInputStream, DataOutputStream | 기본적으로 바이트 스트림을 다루며, 구조화된 데이터는 struct, array 등의 모듈 사용 가능 |
| 객체 직렬화 | ObjectOutputStream (Java 자체 직렬화) | pickle (Python 객체 전용), json, marshal 등 다양한 직렬화 방식 제공 |
| 메모리 기반 IO | ByteArrayInputStream, ByteArrayOutputStream | io.BytesIO |
Java는 스트림 기반 구조를 따르며 FileInputStream, DataOutputStream, ObjectOutputStream 등을 통해 다양한 데이터 타입과 직렬화 객체를 처리합니다.
반면 Python은 open() 함수와 bytes 객체 중심의 간단한 문법으로 더 직관적이고 유연한 바이너리 IO를 구현할 수 있으며, pickle, io.BytesIO 등을 통해 객체 직렬화 및 메모리 기반 작업도 손쉽게 수행할 수 있습니다.
5. 포맷별 입출력 – JSON, CSV, YAML 처리 방식
특정 포맷으로 구조화된 데이터를 다룰 때는 해당 포맷을 파싱하고 생성하는 전용 도구를 사용하는 것이 효율적입니다.
✔️ Java: Jackson, OpenCSV 등 외부 라이브러리 필요
Java는 CSV, JSON, YAML과 같은 특정 데이터 포맷을 다루기 위해 대부분 외부 라이브러리를 사용해야 합니다.
// JSON: Jackson 예시 (build.gradle 또는 pom.xml에 종속성 추가 필요)
// implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
// CSV: OpenCSV 사용 (build.gradle 또는 pom.xml에 종속성 추가 필요)
// implementation 'com.opencsv:opencsv:5.5.2'
import com.opencsv.CSVWriter;
import java.io.FileWriter;
public class FormatIOJavaExample {
public static void main(String[] args) {
// JSON: Jackson 예시
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> data = new HashMap<>();
data.put("name", "Java");
data.put("version", 17);
try {
mapper.writeValue(new File("java_data.json"), data);
System.out.println("Java: 'java_data.json'에 JSON 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: JSON 쓰기 오류: " + e.getMessage());
}
// CSV: OpenCSV 사용
String csvFile = "java_data.csv";
try (CSVWriter writer = new CSVWriter(new FileWriter(csvFile))) {
writer.writeNext(new String[]{"name", "age", "city"});
writer.writeNext(new String[]{"Alice", "30", "Seoul"});
writer.writeNext(new String[]{"Bob", "25", "Busan"});
System.out.println("Java: '" + csvFile + "'에 CSV 쓰기 완료.");
} catch (IOException e) {
System.err.println("Java: CSV 쓰기 오류: " + e.getMessage());
}
}
}
✔️ Python: json, csv - 내장 모듈로 충분
Python은 JSON, CSV와 같은 일반적인 데이터 포맷을 처리하기 위한 강력한 내장 모듈을 제공합니다. YAML과 같은 포맷은 외부 라이브러리(pyyaml)를 사용하지만, 설치가 간단하고 사용법이 직관적입니다.
import json
import csv
import os
# JSON
json_filename = "python_data.json"
json_data = {"name": "Python", "version": 3.10, "type": "language"}
with open(json_filename, "w", encoding="utf-8") as f:
json.dump(json_data, f, indent=4) # indent=4로 예쁘게 출력
print(f"Python: '{json_filename}'에 JSON 쓰기 완료.")
# JSON 읽기
with open(json_filename, "r", encoding="utf-8") as f:
loaded_json_data = json.load(f)
print(f"\nPython: '{json_filename}'에서 JSON 읽기: {loaded_json_data}")
# CSV
csv_filename = "python_data.csv"
csv_data = [
["name", "age", "city"],
["Alice", "30", "Seoul"],
["Bob", "25", "Busan"]
]
with open(csv_filename, "w", newline='', encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(csv_data) # 여러 행을 한 번에 쓰기
print(f"\nPython: '{csv_filename}'에 CSV 쓰기 완료.")
# CSV 읽기
with open(csv_filename, "r", newline='', encoding="utf-8") as f:
reader = csv.reader(f)
loaded_csv_data = list(reader) # 모든 행을 리스트로 읽기
print(f"\nPython: '{csv_filename}'에서 CSV 읽기: {loaded_csv_data}")
# 생성된 파일 정리 (선택 사항)
# os.remove(json_filename)
# os.remove(csv_filename)
✔ 마무리 - IO 전략 선택의 기준
Java와 Python의 파일 입출력 구조는 단순한 문법 차이를 넘어, 언어의 설계 철학과 실행 환경의 차이에서 비롯된 근본적인 전략적 접근의 차이를 보여줍니다.
Java는 다양한 스트림 계층을 조합하여 안정적이고 유연한 IO 흐름을 구성할 수 있도록 설계되었습니다. 이 구조는 서버 개발, 대용량 로그 처리, 고성능 애플리케이션에 적합하며, 예외 처리와 인코딩까지 명확하게 설계된 API 체계를 갖추고 있습니다.
반면 Python은 단 하나의 open() 함수와 with 문만으로 대부분의 IO 작업을 처리할 수 있는 간결함 중심의 설계를 지향합니다. 여기에 pathlib, io, pickle, csv, json 등의 내장 모듈이 결합되면서 복잡한 입출력 흐름도 직관적으로 구현할 수 있습니다.
📌요약
🔸Java는 계층화된 스트림과 명시적 자원 관리를 통해 견고한 IO 구조를 구현할 수 있습니다.
🔸Python은 open() 기반의 일관된 API와 고수준 모듈로 빠른 개발과 유지보수에 적합합니다.
🔸Java는 구조적 유연성을, Python은 사용성 중심의 효율성을 제공합니다.
🔸인코딩, 예외 처리, 버퍼링, 바이너리 IO 등에서 두 언어는 개념적으로 호환되지만 구현 철학은 대조적입니다.
🔸실습을 통해 확인했듯이, 동일한 파일 작업도 언어의 IO 체계에 따라 코드 복잡도, 예외 제어 범위, 실행 성능이 달라집니다.
파일 입출력은 모든 애플리케이션에서 빠질 수 없는 기본 기능입니다. 따라서 단순히 '작동하는 코드'가 아닌, 작업 특성과 언어 특성에 맞는 IO 전략을 선택하는 것이 중요합니다.
Java 개발자라면 Python의 유연한 IO API에 놀라움을 느낄 수 있고, Python 사용자라면 Java의 계층적 스트림 구조에서 강력한 확장성을 발견할 수 있습니다.
양쪽 모두를 경험해보는 것은 더 나은 설계와 효율적인 시스템 구현을 위한 중요한 관점의 확장이 될 것입니다.
※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'3.SW개발 > Python' 카테고리의 다른 글
| [Java관점]11편. Python 프로그래밍 사고 전환 – Java 스타일에서 Python 스타일로 (0) | 2025.11.13 |
|---|---|
| [Java관점]9편. Python 모듈과 import – Java와 다른 가져오기 구조 이해하기 (0) | 2025.11.13 |
| [Java관점]8편. Python 멀티스레딩 vs GIL – Java와 Python 병렬 처리 방식 비교 (0) | 2025.11.13 |
| [Java관점]7편. Python 컬렉션 정리 – Java List/Map/Set과의 대응 구조 (0) | 2025.11.12 |
| [Java관점]6편. Python 예외 처리 문법 – Java와 Python의 처리 구조 비교 (0) | 2025.11.12 |