본문 바로가기
열심히 직장인/Hello World

JAVA로 파일처리 하기 - 스트림(STREAM)의 이해 (DB 데이터로 엑셀 파일 만들기)

by 양파_ 2024. 12. 22.
728x90
반응형

스트림(STREAM)이란?

데이터를 일정한 순서대로 흘려보내는 연속적인 데이터 흐름을 의미한다.

데이터를 한 번에 모두 처리하는 대신, 한 조각씩처리하도록 설계되어 있어 큰 파일이나 네트워크 데이터 처리 시 메모리 효율적이다.

 

파일, 네트워크 연결, 메모리 등 다양한 데이터 소스로 부터 데이터를 읽거나 쓸 때 사용한다.

스트림은 Java에만 국한된 것이 아니라 컴퓨터 과학 전반에서 데이터 처리 방식을 설명할 때 사용하는 일반적인 개념이다. (파이썬에서는 open()함수로, C/C++에서는 FILE* 포인터를 이용하여 스트림 개념을 사용한다.)

 

JAVA에서 사용되는 스트림

자바의 스트림은 InputStream/OutputStream으로 나누어져 입출력을 명확히 구분한다.

데이터를 읽는 스트림(입력 스트림): InputStream 계열 - 외부데이터(파일, 네트워크 등)를 프로그램으로 가져오는 흐름

데이터를 쓰는 스트림(출력 스트림): OutputStream 계열 - 프로그램 데이터를 외부로 보내는 흐름

 

InputStream / OutputStream

-> 스트림을 통해 데이터를 읽고/쓰는 기본 동작을 정의한 추상 클래스로, 자체적으로 사용되지 않고 이를 구현한 하위 클래스(FileInputStream, ByteArrayInputStream 등)를 통해 실제 작업을 수행한다.

 

스프링에서 사용하는 스트림 종류

1. FileInputStream / FileOutputStream

  • 파일 내용을 읽거나 쓰는 데 사용
  • 데이터를 한 바이트씩 처리 (대량 데이터 처리 시 성능 문제)
  • 데이터를 바로 파일로 저장
  • ex) 간단한 테스트 로그 기록

2. BufferedInputStream / BufferedOutputStream

  • 데이터를 버퍼링하여 성능을 향상 (기본값 8KB)
  • 파일이나 네트워크 데이터를 더 빠르게 처리할 때 사용
  • ex) 대규모 엑셀 데이터 저장, HTTP 파일 업로드

3. ByteArrayInputStream / ByteArrayOutputStream

  • 메모리에 있는 데이터(바이트 배열)을 스트림으로 처리
  • 데이터를 메모리에 임시로 저장해 변환하거나 조작한 뒤 나중에 한 번에 처리할 때 사용
  • ex) JSON 생성, 이미지 데이터 처리

4. InputStreamResource

  • 스프링에서 파일이나 데이터 소스를 스트림으로 노출할 때 사용 (ex) REST API 응답으로 파일을 전송)

5. ServletInputStream

  • HTTP 요청으로 본문 데이터를 스트림으로 읽을 때 사용

6. ObjectInputStream / ObjectOutputStream

  • JAVA 객체를 직렬화/역직렬화하여 저장하거나 읽는 데 사용

7. ZipInputStream / ZipOutputStream

  • ZIP 압축된 데이터를 읽고 쓸 때 사용

 

FileInputStream을 이용한 파일 읽기 예시

 

FileInputStream은 디스크에 이진수로 저장된 데이터(파일)을 읽어 메모리로 가져온다.

// 1. 파일과 스트림 연결
FileInputStream fis = new FileInputStream("example.txt");

// 2. 데이터 읽기
// read() 메서드를 호출해 파일의 내용을 한 바이트씩 읽는다. (파일 끝에 도달 시 -1 반환)
int data;
while ((data = fis.read()) != -1) {
	Systsem.out.print((chart) data);  // 바이트 데이터를 문자로 변환 후 출력
}

// 3. 스트림 닫기
fis.close();

 

InputStreamResource를 이용한 파일 다운로드 예시 (REST API)

@GetMapping("/download")
public ResponseEntity<InputStreamResource> download() throws FileNotFoundException {
	File file = new File("example.txt");
    InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
    
    return ResponseEnetity.ok()
    		.contentType(MediaType.APPLICATION_OCTET_STERAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"example.txt\"")
            .body(resource);
}

 

스트림 사용 시 주의사항

.close()를 이용해 명시적으로 닫아주거나, 아래와 같은 try-with-resource 구문을 사용해 스트림이 자동으로 닫히도록 해야한다.

try (FileInputStream fis = new FileInputStream("example.txt")) {
    int data;
    while ((data = fis.read()) != -1) {
        Systsem.out.print((chart) data); 
    }
} // try-with-resources 구문을 이용해 블록 끝에서 자동으로 스트림이 닫힘

 

스트림을 닫아주지 않으면 메모리 누수나 파일 잠금문제 (접근 오류), 시스템 성능이 저하되는 문제가 발생할 수 있다.

 

( 내 경우에 InputStream inputStream = file.getInputStream();을 사용해 파일을 SFTP 서버에 업로드 해주는 로직을 구현했는데, try-with-resource 구문을 사용하지 않고 스트림을 닫아주지도 않아 아래와 같은 오류가 발생했었다. )

java.io.UncheckedIOException: Cannot delete C:\Users\AppData\Local\Temp\tomcat.8086.11889956667311011581\work\Tomcat\localhost\ROOT\upload_fa1e9ec9_b16a_494b_98a3_7c3ddcad23e1_00000001.tmp 
	at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.delete(DiskFileItem.java:427) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
	at org.apache.catalina.core.ApplicationPart.delete(ApplicationPart.java:53) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    .....

 

진행 중인 프로젝트에서 DB에서 데이터를 읽어온 후 화면으로 파일 데이터를 리턴해준 후 저장하게 하는 내용을 구현해야 했다. 이 때 엑셀 파일 생성을 위해 사용한 라이브러리는 Apach POI 였다.

 

위에 나열한 여러 종류의 스트림 중 이와 같은 상황에 가장 적합한 유형은 무엇일까?

=> BufferedOutputStream

 

그 이유는 한꺼번에 많은 데이터를 메모리에 적재하지 않고 스트림으로 조금씩 처리하여 메모리 사용량을 줄일 수 있기 때문이다. 또한 Apache POI와 같은 엑셀 라이브러리와 호환성도 좋다.

 

 

여기서 다시 궁금한게 생겼다.

BufferedOutputStream이 그렇게 좋으면, 모든 경우에 그걸 사용하면 되는 게 아닐까?

GPT에게 물어본 답은 모든 경우에 적합한 것은 아니라고 한다. 

그 이유는

  1. 작은 데이터 처리에는 과도함 : 데이터 양이 작거나 단순히 메모리에서 처리 하는 경우 버퍼링 오버헤드가 발생한다.
  2. 메모리 데이터 조작 불가: BufferedOutputStream은 메모리 데이터를 직접 조작할 수 없는 반면 ByteArrayOutputStream은 메모리 내 데이터를 조작하고 읽기/쓰기 작업을 동시에 처리할 수 있다.

 

그러면 다시.. 

내가 작성해야 하는 엑셀 파일은 저장되는 데이터가 클 수도 있고 작을 수도 있고 상황에 따라 다른데 그래도 BufferedOutputStream이 가장 좋은 것일까?
-> 소량의 데이터에 약간의 오버헤드가 생길 수도 있지만 대부분의 경우 무시할 수 있는 경우이며, 대량의 데이터에는 필수적이므로 이런 경우에도 BufferedOutputStream을 사용하는 것이 안전하고 효율적이라고 한다.

 

반응형