[Android] java.io.stream에 대한 고찰

2026. 3. 15. 01:26·Android

👋 들어가며

안녕하세요! ✋

사이드 프로젝트 동아리 DDD에서 Android 개발자로 활동하고 있는 오세민입니다.

이번 글에서는 Java 개발을 하다 보면 반드시 마주치게 되는 주제,
Stream, InputStream, OutputStream의 설계 철학에 대해 이야기해보려고 해요.

특히,

  • Stream이라는 개념이 왜 존재하는지
  • 왜 입력과 출력이 분리되어 있는지
  • flush()와 close()는 왜 필요한지

이런 질문들을 중심으로
"왜 이렇게 설계되었을까?"라는 질문에서 시작해 보려고 합니다.

 


🧭 Stream은 뭘까요

Stream이라는 단어를 처음 들으면 Java 8의 Stream API가 떠오를 수도 있어요.
하지만 여기서 이야기하는 Stream은 그것보다 훨씬 근본적인 개념이에요.

Stream은 "데이터의 흐름(Flow)"을 추상화한 구조예요.

Java의 설계 철학은 이래요.

모든 입출력은 Stream을 통해 이루어진다.

파일이든, 네트워크든, 디바이스든
동일한 방식으로 다룰 수 있도록
데이터를 '흐름(Stream)'이라는 개념으로 통일한 거예요.

그래서 Stream을 이렇게 정리할 수 있어요.

Stream = "JVM ↔ OS 간 데이터 통로"

JVM이 OS의 입출력 시스템(파일, 네트워크, 디바이스 등)과 통신하기 위한
통일된 인터페이스라고 보면 돼요.

 


🔌 실제로는 어떤 리소스와 연결될까요

Stream이라는 하나의 추상화 아래에는
실제로 다양한 리소스가 연결되어 있어요.

실제 리소스 Stream 구현체 OS 레벨 매핑
파일 FileInputStream / FileOutputStream file descriptor (fd)
네트워크 소켓 SocketInputStream / SocketOutputStream socket fd
파이프 PipedInputStream / PipedOutputStream pipe fd
메모리 ByteArrayInputStream / ByteArrayOutputStream JVM heap 메모리

파일을 읽든, 소켓으로 데이터를 보내든,
코드에서 다루는 방식은 동일해요.

이게 바로 Stream이라는 추상화가 가져다주는 힘이에요.

 


↔️ 왜 InputStream과 OutputStream으로 나뉘어 있을까요

Java는 Stream을 단방향(one-way)으로 설계했어요.

클래스 방향 주요 메서드
InputStream 입력 (read) read(), read(byte[])
OutputStream 출력 (write) write(), flush(), close()

이렇게 분리한 이유는 명확해요.

데이터 흐름의 책임을 명확히 하고,
예측 가능한 I/O를 보장하기 위해서예요.

"이 Stream은 읽기만 한다" 또는 "이 Stream은 쓰기만 한다"가
코드 레벨에서 명확하게 드러나는 거예요.

양방향 통신이 필요한 Socket 같은 경우에도
내부적으로는 InputStream + OutputStream 쌍으로 처리해요.

"데이터는 한 방향으로만 흐른다"는 철학을 일관되게 지키는 거예요.

 


🏗️ 왜 추상 클래스로 설계했을까요

InputStream과 OutputStream은 인터페이스가 아니라 추상 클래스예요.

그 이유는 이래요.

파일, 네트워크, 메모리 등
다양한 데이터 소스를 동일한 인터페이스로 다루기 위해서예요.

구조를 보면 이렇게 생겼어요.

            InputStream / OutputStream  ← 추상화 계층 (Closeable)
                         │
     ┌───────────────────┼─────────────────────┐
     │                   │                     │
FileInputStream     SocketInputStream     ByteArrayInputStream
(fd 기반)            (fd 기반)              (메모리 기반)

모든 Stream은 동일한 추상 메서드를 통해 동작하지만,
각 구현체는 자신이 다루는 리소스(fd, socket, memory)에 따라
실제 I/O 방식을 다르게 정의해요.

덕분에 우리는 "이게 파일인지 소켓인지"를 신경 쓰지 않고
동일한 코드로 데이터를 읽고 쓸 수 있어요.

 


🔒 Closeable을 상속한 이유가 뭘까요

Stream이 OS 리소스와 연결된 이상,
그 통로(fd)는 커널에 의해 관리되며 유한한 리소스예요.

Closeable을 상속한 이유는 명확해요.

Stream이 열리면 OS에 리소스가 등록되고,
close()는 그 리소스를 OS에 반납해요.

단계별로 보면 이래요.

단계 동작 대응되는 OS 동작
Stream 생성 OS에 open() syscall 호출 fd 할당
데이터 I/O read()/write() 호출 커널 버퍼를 통한 I/O
close() 호출 OS에 close(fd) syscall fd 해제, 커널 리소스 반납

그래서 close()는 이렇게 이해하면 돼요.

close() = "JVM이 OS에게 이제 이 통로(fd)를 닫아도 좋습니다"라는 신호

close()를 호출하지 않으면
fd가 계속 열린 채로 남아서 리소스 누수(resource leak)가 발생해요.

이건 단순히 메모리 문제가 아니라,
OS 레벨의 리소스 고갈로 이어질 수 있는 심각한 문제예요.

 


🚿 flush는 단순히 "버퍼를 비우는 것"이 아니에요

flush()를 "버퍼를 비운다"고만 알고 계신 분이 많을 거예요.
하지만 조금 더 정확하게 말하면 이래요.

flush() = "지연된 I/O를 즉시 수행하여
논리적 상태와 물리적 상태를 일치시키는 행위"

데이터는 여러 단계의 버퍼를 거쳐요.

[앱 코드] → [JVM 버퍼] → [OS 커널 버퍼] → [디스크/네트워크]
계층 flush 대상 대표 메서드
JVM 버퍼 BufferedOutputStream flush()
OS 버퍼 FileDescriptor sync() (fsync(fd))

flush()는 "현재 계층의 데이터를 다음 계층으로 강제 전송(commit)"하는 역할을 해요.

쉽게 말하면,

flush() = "이제 진짜로 보내라"

JVM 버퍼를 OS 커널로,
OS 버퍼를 디스크로 내보내는 타이밍 제어 수단이에요.

네트워크 통신에서 flush()를 빼먹으면
데이터가 버퍼에 머물러서 상대방에게 도착하지 않는 경우도 있어요.

그래서 flush()는 데이터 일관성을 보장하는 중요한 메서드예요.

 


🧩 설계 철학을 한눈에 정리하면요

항목 의미
Stream 데이터 흐름(Flow)의 추상화. JVM ↔ OS I/O의 논리적 통로
InputStream / OutputStream OS의 read/write 개념을 분리하여 표현
Closeable OS 리소스(fd, socket 등)를 명시적으로 해제하는 계약
flush() 지연된 I/O를 즉시 커밋하여 데이터 일관성 보장
단방향성 데이터 흐름의 책임과 안정성을 확보하기 위한 구조
추상화 목적 파일, 네트워크, 메모리 등 모든 I/O를 일관된 인터페이스로 다루기 위함

 


✅ 정리해보면 이렇습니다

  • Stream은 "JVM과 OS 사이의 통로"예요
  • InputStream과 OutputStream은 그 통로를 통해 데이터를 "읽고/쓰는 방향"을 정의해요
  • flush()는 데이터를 다음 계층으로 커밋하는 시점 제어 도구예요
  • close()는 OS 리소스를 해제하는 명시적 종료 신호예요

결국 Stream은 "I/O를 일관된 추상화로 표현한 계약(Contract)"이며,
Java의 일관성과 안전성 철학을 가장 잘 보여주는 구조예요.

무엇을 쓰느냐보다
왜 이런 구조가 존재하는지 이해하는 게 더 중요해요.

I/O는 조용히 동작하지만,
문제가 생기면 고치기 어렵거든요.

긴 글 읽어주셔서 감사합니다.

 


🔗 레퍼런스

이 글을 정리하면서 실제로 참고한 자료들이에요.

  • Java InputStream - Oracle Docs
  • Java OutputStream - Oracle Docs
  • Closeable - Oracle Docs
  • Java I/O Tutorial - Oracle

'Android' 카테고리의 다른 글

[Android] joda‑time, java.time, kotlinx‑datetime에 대하여..  (0) 2026.02.14
'Android' 카테고리의 다른 글
  • [Android] joda‑time, java.time, kotlinx‑datetime에 대하여..
dynamic-ddd
dynamic-ddd
역할의 경계를 넘어 유저에게 도달하는 프로덕트를 완성해온 메이커들의 커뮤니티입니다.
  • 인기 글

  • dynamic-ddd
    dynamic-ddd 님의 블로그
    dynamic-ddd
  • 전체
    오늘
    어제
    • 분류 전체보기 (9)
      • iOS (5)
      • Android (2)
      • Design (2)
  • 공지사항

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
dynamic-ddd
[Android] java.io.stream에 대한 고찰
상단으로

티스토리툴바