자바에서 입출력(I/O)은 생각보다 매우 단순합니다. 네트워크든 파일이든 결국 하나의 입구(InputStream)와 하나의 출구(OutputStream)로 통일됩니다.
InputStream과 OutputStream은 바이트 스트림으로
이 글에서는 특히 자주 사용되는 Socket과 File입출력을 중심으로 Java의 I/O 구조를 쉽게 살펴보겠습니다.
InputStream과 OutputStream 바이트 스트림
자바의 I/O 핵심은 InputStream과 OutputStream입니다. 이들은 모든 바이트 스트림의 최상위 추상 클래스이며, 데이터를 읽거나 쓸 수 있는 공통 메서드를 제공합니다.
- InputStream: 데이터 입력(읽기, 수신)을 위한 스트림
- OutputStream: 데이터 출력(쓰기, 송신)을 위한 스트림
모든 구현체는 결국 이 두 스트림에서 파생되어 만들어지며, 메서드 이름도 동일합니다.
Socket 입출력 바이트 스트림
Socket은 네트워크에서 TCP/IP 기반의 데이터를 주고받기 위한 연결 통로입니다.
Socket socket = new Socket("0.0.0.0", 80);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
SocketInputStream, SocketOutputStream 등의 내부 구현 클래스가 사용되며, 실제 데이터 처리는 OS가 담당합니다.
JVM은 직접 socket을 연동할 수 없으며, JVM 내부의 native 레이어(예: socketRead0등)가 OS 소켓 API를 호출
File 입출력 바이트 스트림
파일 시스템과의 입출력 역시 간단합니다.
InputStream in = new FileInputStream("file.txt");
OutputStream out = new FileOutputStream("output.txt");
역시 내부적으로 OS의 파일 API를 호출해 데이터를 주고받습니다.
자주 사용하는 InputStream, OutputStream 구현체
| InputStream 구현체 | 용도 |
| FileInputStream | 파일 읽기 |
| SocketInputStream | 네트워크 데이터 읽기 |
| BufferedInputStream | 버퍼링을 통해 성능을 향상 |
| DataInputStream | 기본 자료형 단위로 읽기 |
| ByteArrayInputStream | 메모리에서 읽기 |
| OutputStream 구현체 | 용도 |
| FileOutputStream | 파일 쓰기 |
| SocketOutputStream | 네트워크 데이터 쓰기 |
| BufferedOutputStream | 버퍼링을 통해 성능 향상 |
| DataOutputStream | 기본 자료형 단위로 쓰기 |
| ByteArrayOutputStream | 메모리로 쓰기 |
Reader와 Writer 문자를 다루는 고수준 스트림 유틸리티
InputStream과 OutputStream을 사용하면서 문자열을 주고받다보면 코드의 가독성이 떨어지게 됩니다.
그래서 나온 바이트 데이터를 문자 형태로 편리하게 사용할 수 있도록 해주는 유틸리티가 있습니다.
- Reader: 입력된 바이트를 문자로 반환
- Writer: 문자를 바이트로 변환하여 출력
대표적으로 InputStreamReader, OutputStreamWrtier가 있습니다.
Reader reader = new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);
Writer writer = new OutputStreamWriter(new Socket("localhost", 123).getOutputStream());
파일이나 네트워크 (또는 메모리, 파이프) 상관없이 자바의 I/O는 모두 사용가능합니다.
InputStream, OuputStream 과 Reader, Writer의 활용
아래 코드는 많이 복잡해보인지만 input, output (I/O) 로 보면 꽤나 단순한 코드입니다.
File file = new File("text.txt"); // 자원
FileOutputStream fos = new FileOutputStream(file); // Output 스트림
PrintWriter pw = new PrintWriter(fos); // Writer 유틸리티
HttpURLConnection connection = (HttpURLConnection) new URL("http://www.naver.com").openConnection(); // 자원
InputStream httpInputStream = connection.getInputStream(); // Input 스트림
PrintStream printStream = System.out; // Output 스트림
InputStream consoleInputStream = System.in; // Input 스트림
Scanner scanner = new Scanner(consoleInputStream); // Reader 유틸리티
File, HTTP, Console 등을 다양하게 썻지만 아래 구조는 바뀌지 않습니다.
구조 : [output 스트림] -> [ 자원(통로) ] -> [input 스트림]
. (필요시 writer) (필요시 reader)
자주 사용하는 Reader, Writer 구현체
| Reader 구현체 | 용도 |
| FileReader | 텍스트 파일 읽기(내부적으로 stream 구현) |
| BufferedReader | 라인 단위 읽기(성능 향상) |
| InputStreamReader | 바이트를 문자로 변환 |
| StringReader | 문자열로부터 읽기 |
| Scanner | 토큰 단위 파싱용 고수준 읽기 Reader를 직접 상속하지 않지만 Reader/InputStream을 감싸서 사용 |
| Writer 구현체 | 용도 |
| FileWriter | 텍스트 파일 쓰기 |
| BufferedWriter | 버퍼링을 통해 성능 향상 |
| OutputStreamWriter | 문자를 바이트로 변환 |
| PrintWriter | 간편한 텍스트 출력 |
| StringWriter | 문자열을 메모리 상에 저장하는 Writer |
정리
JDBC, JPA, Spring Servlet, 그리고 Socket, HTTP 통신까지 우리가 일상적으로 사용하는 거의 모든 자바 기술은 InputStream/OutputStream(또는 Reader/Writer)위에서 동일한 방식으로 동작합니다.
자원이 파일이거나 네트워크 소켓, 메모리, 버퍼, 파이프 모두 입구(입력)와 출구(출력)은 언제나 하나라는 점이 자바 I/O의 가장 큰 장점입니다.
이 구조를 이해하고 나면, 원하는 자원만 바꿔 끼우는 것만으로도 로킹, 버퍼링, 압축, 암호화와 같은 부가 기능을 자유롭게 조합할 수 있습니다. 결국 "입력 -> 가공 -> 출력" 이라는 단순한 패턴만 기억하면, 어떤 I/O 시나리오든 손쉽게 설계하고 구현할 수 있습니다.
'Architecture' 카테고리의 다른 글
| 대규모 채팅 서비스 개발에 대한 회고 (2) | 2025.07.15 |
|---|---|
| 선착순 이벤트 아키텍처 구성 (2) | 2025.05.30 |
| CDN 이란? 글로벌 서비스 캐싱 전략 (0) | 2025.04.19 |
| Spring Security 없이 OAuth2 클라이언트 직접 구현 (0) | 2025.04.13 |
| OAuth2 인증 Flow (0) | 2025.04.12 |