Architecture

Java 에서의 데이터 입출력(I/O), Byte Stream

백엔드 유성 2025. 5. 20. 21:15

자바에서 입출력(I/O)은 생각보다 매우 단순합니다. 네트워크든 파일이든 결국 하나의 입구(InputStream)와 하나의 출구(OutputStream)로 통일됩니다.

InputStream과 OutputStream은 바이트 스트림으로

이 글에서는 특히 자주 사용되는 Socket과 File입출력을 중심으로 Java의 I/O 구조를 쉽게 살펴보겠습니다.

 

InputStream과 OutputStream 바이트 스트림

자바의 I/O 핵심은 InputStreamOutputStream입니다. 이들은 모든 바이트 스트림의 최상위 추상 클래스이며, 데이터를 읽거나 쓸 수 있는 공통 메서드를 제공합니다.

  • 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 시나리오든 손쉽게 설계하고 구현할 수 있습니다.