[Java] 파일 다운로드 구현 방법 총 정리

1. 개요

스프링 프레임워크 기반으로 개발을 하면서 파일 다운로드를 할 때
다양한 방법으로 적용할 수 있어서 볼 때마다 새로이 다시 공부하는 느낌이였다.

오늘은 내가 보기 편하게 찾을 수 있도록 
Http프로토콜에서 파일 다운로드를 하는 방법을 정리하고자 한다.

 

※참고로 구현 가능한 기술들의 동향이 달라지기 때문에 변수가 존재하는 점을 참고해야 한다.

2. 내장 기능을 이용한 구현 방법

- 스프링 내장 기능을 이용한 다운로드 방법

스프링 프레임워크에서 HttpHeaders와 ResponseEntity를 이용하여 구현한다.

더보기
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

@Controller
public class FileDownloadController {

    @GetMapping("/download/{fileName}")
    public ResponseEntity<FileSystemResource> downloadFile(@PathVariable String fileName) throws IOException {
        //실제 파일의 경로를 읽어서 File객체를 생성한다
        File file = new File("your_directory_path/" + fileName);

        //응답 헤더 설정을 위한 HttpHeaders객체를 생성한다.
        HttpHeaders headers = new HttpHeaders();

        //생성된 응답 헤더 객체의 컨텐츠 타입을 바이너리 데이터로 설정한다.
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

        /*응답의 Content-Disposition헤더를 설정한다.
        headers.setContentDispositionFormData(응답형태, 파일명)

        - param1: attachment는 응답을 첨부파일로 처리한다.
        - param2: fileName는 첨부파일의 명칭을 직접 지정한다.*/
        headers.setContentDispositionFormData("attachment", fileName);

        /*파일 다운로드를 위한 ResponseEntity객체를 생성 및 반환한다.
        new ResponseEntity<>(다운로드할 파일, 응답 헤더, 응답 상태코드);
        
        - param1: new FileSystemResource(file)는 다운로드할 파일을 읽어온다.
        - param2: headers는 설정된 HttpHeaders 응답 헤더
        - param3: org.springframework.http.HttpStatus.OK는 응답 상태가 성공적임을 나타내는 200(OK)코드
        */
        return new ResponseEntity<>(new FileSystemResource(file), headers, org.springframework.http.HttpStatus.OK);
    }
}

스프링 프레임워크에서 StreamingResponseBody를 클라이언트에게 데이터를 스트리밍하도록 구현한다.

더보기
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

@RestController
public class FileDownloadController {

    @GetMapping("/downloadStreamingResponseBody/{fileName}")
    public StreamingResponseBody downloadFileWithStreamingResponseBody(@PathVariable String fileName) throws IOException {
        //실제 경로를 읽어서 다운로드할 파일을 나타내는 File객체를 생성한다.
        File file = new File("your_directory_path/" + fileName);
        
        //파일을 읽기 위한 FileInputStream객체를 생성한다.
        FileInputStream fileInputStream = new FileInputStream(file);
        
        /*파일을 읽고 해당 데이터를 클라이언트로 전송하기위해 사용되는 outputStream객체를 사용한다.
        파일을 4096바이트 크기의 버퍼를 사용하여 읽어들이고, outputStream을 통해 클라이언트에게 전송한다.
        그리고 파일을 모두 읽은 후에는 fileInputStream을 닫아준다.
        */
        return outputStream -> {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            fileInputStream.close();
        };
    }
}

- 스프링 부트(SPRING BOOT)를 이용한 다운로드 방법

스프링 부트를 이용하면 스프링을 이용한 구현 방법이 더 간결해진다.

더보기
@RestController
public class FileDownloadController {

    @GetMapping("/downloadSpringBoot/{fileName}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) throws IOException {
        /*FileSystemResource를 사용하여 파일을 나타내는 Resource객체를 생성한다.
        스프링에서 제공되는 Resource객체는 인터페이스를 구현하는 클래스로 파일시스템의 리소스를 나타낸다 */
        Resource resource = new FileSystemResource("your_directory_path/" + fileName);

        /*ResponseEntity를 생성하여 클라이언트에게 파일 다운로드 응답을 생성하고 --> 응답 헤더의 Content-Disposition에 따라 파일명과 다운로드방식을 지정하여 클라이언트에게 응답을 전송한다.
        .ok()는 HTTP 상태 코드를 OK(200)으로 설정한다.
        .header는 응답 헤더의 Content-Disposition을 설정한다
        .body(resource)는 응답의 본문(body)에 'resource'를 설정한다.
        */
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
}

- 아파치(Apache)를 이용한 다운로드 방법

아파치는 image, js, html, css등 정적인 리소스 파일만을 서비스하기 위한 웹 서버이다.
src/webapp 경로에 다운로드 파일을 지정시키고 해당 디렉토리 경로를 URL로 실행시키면 다운로드가 된다.
다만, 위의 경우 보안상의 문제가 발생할 수 있다는 점을 참고하자. (이 방법은 아파치를 이용)

그리고 src/webapp/WEB-INF 경로의 경우에는 직접 접근할 수 없고, 무조건 Controller를 거쳐야만 사용자가 접근할 수 있는 경로이다. (이 방법은 톰캣을 이용)

때문에 프로젝트 소스 내에 Temp파일을 다운로드 한다면 직접 다운로드할 수 있는(=보안에 위배되는) webapp에 지정하지 않고 WEB-INF에 지정시키고 Controller를 거쳐서 다운로드를 하는 방법이 바람직하다.

- 톰캣(Tomcat)을 이용한 다운로드 방법

톰캣은 주로 Java 웹 애플리케이션의 서버로 사용되어, 파일 다운로드는 서블릿을 사용하여 구현한다.

더보기
@WebServlet("/download")
public class FileDownloadServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //먼저 다운로드할 파일의 경로를 지정한다.
        String filePath = "your_directory_path/your_file.txt";
        
        //다운로드할 파일을 나타내는 File객체를 생성한다.
        File downloadFile = new File(filePath);

        //FileInputStream객체를 사용하여 파일을 읽기 위한 스트림을 생성한다.
        try (FileInputStream fis = new FileInputStream(downloadFile)) {
            /*HTTP응답의 Content-type 헤더를 설정한다. 
            일반적으로 바이너리 데이터를 나타내는 application/octet-stream를 사용한다.*/
            response.setContentType("application/octet-stream");
            
            /*HTTP응답의 Content-Length 헤더를 설정한다.
            다운로드할 파일의 크기를 지정한다.*/
            response.setContentLength((int) downloadFile.length());
            
            //Content-Disposition헤더를 설정하여 브라우저가 응답을 파일로 처리하도록 지시한다.
            response.setHeader("Content-Disposition", "attachment; filename=\"" + downloadFile.getName() + "\"");

            //HTTP응답의 출력 스트림을 얻는다.
            try (OutputStream os = response.getOutputStream()) {
                /*주어진 파일을 HTTP응답으로 전송하여 
                파일을 읽어서 버퍼에 담고 --> 버퍼의 내용을 
                출력 스트림(os)을 통해 클라이언트에게 전송하는 과정을 반복하여 파일의 모든 내용을 전송한다*/
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
            }
            
            System.out.println("여기까지 실행하는데 문제없으면 다운로드 성공");            
        }
    }
}

- 자바 내장기능을 이용한 다운로드 방법

순수 자바 언어를 이용한 다운로드는 HttpConnection를 사용하여 구현한다.

더보기
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;

public class FileDownloadExample {
    public static void main(String[] args) {
        //다운로드할 파일의 URL을 지정한다.
        String fileURL = "https://example.com/your_file.txt";
        
        //다운로드할 파일을 저장할 디렉토리를 지정한다.
        String saveDir = "your_directory_path/";

        try {
            //다운로드할 파일의 URL을 URL객체로 변환한다.
            URL url = new URL(fileURL);
            
            //URL을 통해 연결을 열고, 연결에 대한 URLConnection객체를 얻는다.
            URLConnection connection = url.openConnection();
            
            //연결로부터 입력스트림을 얻어서 BufferedInputStream으로 감싸준다.
            BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
            
            //다운로드한 데이터를 저장할 파일의 출력 스트림을 생성한다.
            FileOutputStream fileOutputStream = new FileOutputStream(saveDir + "your_file.txt");

            //데이터를 1024바이트씩 읽어오고 --> 읽어온 데이터를 파일 출력 스트림을 통해 저장한다.
            byte[] dataBuffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
                fileOutputStream.write(dataBuffer, 0, bytesRead);
            }
            System.out.println("여기까지 실행하는데 문제없으면 다운로드 성공");         
            
            //파일 출력 스트림과 입력 스트림을 닫는다.
            fileOutputStream.close();
            in.close();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

- Servlet 내장기능을 이용한 다운로드 방법

순수 자바 웹 애플리케이션을 이용한 다운로드 구현.

더보기
@WebServlet("/downloadServlet")
public class FileDownloadServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*다운로드할 파일의 경로를 지정한다
        경로는 Nas서버나 로컬에 따라서 달라질 수 있다*/
        String filePath = "your_directory_path/your_file.txt";
        
        //다운로드할 파일을 나타내는 File객체를 생성한다.
        File downloadFile = new File(filePath);

        //파일을 읽기 위한 FileInputStream을 생성한다.
        try (FileInputStream fis = new FileInputStream(downloadFile);
             //생성된 FileInputStream을 BufferedInputStream으로 감싼다.
             BufferedInputStream bis = new BufferedInputStream(fis);
             
             //클라이언트로 데이터를 출력하기 위한 OutputStream을 얻기 위해 try-with-resources를 사용한다.
             OutputStream os = response.getOutputStream()) {

             /*HTTP응답의 Content-Type헤더를 설정한다.
             일반적으로 바이너리 데이터를 나타내는 application/octet-stream를 사용한다*/
             response.setContentType("application/octet-stream");
             
             //HTTP응답의 Content-Length헤더를 설정하여 다운로드할 파일의 크기를 지정한다.
             response.setContentLength((int) downloadFile.length());
             
             //Content-Disposition헤더를 설정하여 브라우저가 응답을 파일로 처리하도록 지시한다.
             response.setHeader("Content-Disposition", "attachment; filename=\"" + downloadFile.getName() + "\"");

             /*파일을 읽어서 버퍼에 담고 -> 버퍼의 내용을 출력 스트림을 통해 클라이언트에게 전송한다
             이 과정을 반복하여 파일의 모든 내용을 클라이언트에게 전송한다.*/            
             byte[] buffer = new byte[4096];
             int bytesRead;
             while ((bytesRead = bis.read(buffer)) != -1) {
                 os.write(buffer, 0, bytesRead);
             }
             
             System.out.println("여기까지 실행하는데 문제없으면 다운로드 성공");
        }
    }
}

 

3. 라이브러리를 이용한 구현 방법

- Apache Commons IO 라이브러리를 이용한 다운로드 방법

Maven 또는 Gradle에서 org.apache.commons:commons-io:2.11.0 라이브러리를 추가가 되어 있는 상태에서 FileUtils를 이용하여 구현한다.

더보기
import org.apache.commons.io.FileUtils;

public class FileDownloadUsingCommonsIO {
    public static void main(String[] args) {
        //다운로드 할 파일의 URL을 지정한다
        String fileURL = "https://example.com/your_file.txt";
        
        //다운로드한 파일을 저장할 경로를 지정한다.
        String saveDir = "your_directory_path/";

        //파일 다운로드 시 예외 처리를 위한 try 블록을 감싼다.
        try {
            /*Apache Commons IO 라이브러리의 FileUtils.copyURLToFile메서드를 사용한다.
            copyURLToFile는 주어진 URL에서 파일을 다운로드하고, 지정된 경로에 저장하는 기능을 제공한다*/
            FileUtils.copyURLToFile(new URL(fileURL), new File(saveDir + "your_file.txt"));
            
            System.out.println("여기까지 실행하는데 문제없으면 다운로드 성공");            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

- OkHttp 라이브러리를 이용한 다운로드 방법

OkHttp는 안드로이드 및 Java에서 HTTP통신을 쉽게 처리할 수 있는 라이브러리이다.
Maven 또는 Gradle에서 com.squareup.okhttp3:okhttp:4.9.0 라이브러리를 추가하여 구현한다.

더보기
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class FileDownloadUsingOkHttp {
    public static void main(String[] args) {
        //다운로드 할 파일의 URL을 지정한다.
        String fileURL = "https://example.com/your_file.txt";
        
        //다운로드한 파일을 저장할 경로를 지정한다.
        String saveDir = "your_directory_path/";

        /*OkHttpClient객체를 생성한다.
        이는 Square에서 제공하는 OkHttp라이브러리를 사용하여 HTTP요청을 처리하는데 사용한다*/
        OkHttpClient client = new OkHttpClient();
        
        /*OkHttp의 Request.Builder를 사용하여 HTTP요청을 생성한다.
        여기서 주어진 URL로 GET요청을 생성한다 */
        Request request = new Request.Builder().url(fileURL).build();

        /*OkHttp를 사용하여 서버에 HTTP요청을 보내고 -> 응답을 받아오기 위해 try-with-resources블록을 시작한다.
        여기서는 execute() 메서드를 통해 동기적으로 요청을 실행하고 응답을 얻습니다*/        
        try (Response response = client.newCall(request).execute()) {
        
            //응답(response)에서 바디를 얻는다.
            ResponseBody body = response.body();
            
            //응답 바디가 null이 아닌 경우에만 코드 블록을 실행합니다.
            if (body != null) {
            
                /*응답 바디에서 InputStream을 얻어 파일을 읽는다.
                그리고 FileOutputStream을 사용하여 파일을 저장할 try-with-resources 블록을 시작한다*/                
                try (InputStream inputStream = body.byteStream();
                     FileOutputStream fileOutputStream = new FileOutputStream(saveDir + "your_file.txt")) {

                    /*파일을 읽어서 (4096크기의 바이트 단위) 버퍼에 담는다.
                    그리고 버퍼의 내용을 출력 스트림을 통해 클라이언트에게전송한다.
                    이 과정을 반복하여 파일의 모든 내용을 클라이언트에게 전송한다. */                    
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        fileOutputStream.write(buffer, 0, bytesRead);
                    }
					
                    System.out.println("여기까지 실행하는데 문제없으면 다운로드 성공");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

4. 멀티(압축) 다운로드 방법

- 스프링을 이용한 (여러개를 전송) 다운로드 방법1

서버에서 여러 개의 파일을 클라이언트에게 직접 전송하는 방식

다시 말하자면,
서버에서는 클라이언트에게 다건 파일을 전송하고 -> 브라우저에서 응답헤더에 의해 압축형태로 내려받는 방식이다.
하지만, 상황에 따라 다건을 낱개로 내려받을 수 있다.

더보기
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Controller
public class MultiFileDownloadController {

    @GetMapping("/multiDownload")
    public ResponseEntity<List<FileSystemResource>> multiFileDownload() throws IOException {
        /*FileSystemResource 객체를 리스트에 추가하는 부분이다. 
        다운로드할 여러 파일을 resources 리스트에 추가한다. 
        여기서는 "file1.txt" 및 "file2.txt" 파일을 추가하고 있다.*/
        List<FileSystemResource> resources = new ArrayList<>();
        resources.add(new FileSystemResource("your_directory_path/file1.txt"));
        resources.add(new FileSystemResource("your_directory_path/file2.txt"));

        /*응답 헤더를 설정. 
        Content-Type을 바이너리 데이터로 지정하고, 
        Content-Disposition을 설정하여 브라우저가 응답을 "files.zip"으로 처리하도록 지시한다*/
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", "files.zip");


        /*ResponseEntity를 사용하여 HTTP 응답을 생성한다. 
        headers로 설정한 응답 헤더와 다운로드할 파일 목록(resources)을 응답 본문으로 설정한다. 
        ok() 메서드로 HTTP 상태 코드를 200 (OK)로 설정한다.*/
        return ResponseEntity.ok()
                .headers(headers)
                .body(resources);
    }
}

- 스프링을 이용한 (한번에 처리) 다운로드 방법2

여러 파일을 서버에서 압축하여 클라이언트에게 압축 파일 하나를 전송하는 방식.


다시 말하자면,
서버에서는 클라이언트에게 다건 파일을 압축 전송하고 -> 브라우저에서는 압축된 파일을 전달받는 방식.

더보기
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Controller
public class ZipFileDownloadController {

    @GetMapping("/zipDownload")
    public ResponseEntity<FileSystemResource> zipFileDownload() throws IOException {
        List<FileSystemResource> resources = new ArrayList<>();
        resources.add(new FileSystemResource("your_directory_path/file1.txt"));
        resources.add(new FileSystemResource("your_directory_path/file2.txt"));

        /*압축된 ZIP 파일을 생성할 File 객체를 생성한다.
        그리고 경로에 해당하는 압축파일이 생성된다.*/
        File zipFile = new File("your_directory_path/files.zip");
        
        //ZipOutputStream을 사용하여 ZIP 파일을 생성하는 try-with-resources 블록을 시작에서 압축파일을 생성한다.
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
           
            /*resources 리스트에 있는 각 FileSystemResource에 대해 반복한다 
            resources는 이전 코드에서 생성한 파일 목록*/
            for (FileSystemResource resource : resources) {
                //현재 처리 중인 FileSystemResource의 정보를 기반으로 ZipEntry를 생성하고 ZipOutputStream에 추가한다
                ZipEntry zipEntry = new ZipEntry(resource.getFilename());
                zipOutputStream.putNextEntry(zipEntry);

                /*현재 처리 중인 FileSystemResource의 내용을 읽어들여 ZIP 파일에 쓰는 작업을 수행한다. 
                resource.getInputStream()으로부터 데이터를 읽어 들이고, zipOutputStream을 통해 ZIP 파일에 쓰기를 수행한다*/
                byte[] data = new byte[1024];
                int bytesRead;
                while ((bytesRead = resource.getInputStream().read(data)) != -1) {
                    zipOutputStream.write(data, 0, bytesRead);
                }

                //현재의 ZipEntry 처리를 마치고, 다음 ZipEntry로 넘어가기 전에 현재 엔트리를 닫습니다.
                zipOutputStream.closeEntry();
            }
         }

         HttpHeaders headers = new HttpHeaders();
         headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
         headers.setContentDispositionFormData("attachment", "files.zip");

         return ResponseEntity.ok()
                 .headers(headers)
                 .body(new FileSystemResource(zipFile));
    }
}

- 자바를 이용한 (여러개를 전송) 다운로드 방법1

여러 개의 파일을 각각 다운로드하고 저장하는 방식으로, 파일 간의 독립성이 있는 방식이다.
때문에, 압축이 아니라 낱개로 여러 개를 다운로드 받을 수도 있다.

더보기
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

public class MultiFileDownload {

    public static void main(String[] args) {
        //파일들의 URL목록을 나타내는 List를 생성하고 초기화한다.
        List<String> fileURLs = Arrays.asList(
                "https://example.com/file1.txt",
                "https://example.com/file2.txt"
        );

        //각 파일의 URL에 대해 forEach 메서드를 사용하여 루프를 돌린다.
        fileURLs.forEach(url -> {
         
            /*URL을 통해 입력 스트림(InputStream)을 열고 해당 URL에서 파일의 내용을 읽는다. 
            동시에 출력 스트림(FileOutputStream)을 생성하여 로컬 파일 시스템에 파일을 저장할 경로를 설정한다. 
            그리고 try-with-resources 블록을 사용하여 자동으로 스트림을 닫는다*/
            try (InputStream in = new URL(url).openStream();
                 FileOutputStream fos = new FileOutputStream("your_directory_path/" + getFileName(url))) {
                 
                /*파일의 내용을 읽어들여 1024바이트씩 버퍼에 담고, 버퍼의 내용을 출력 스트림을 통해 로컬 파일에 쓰는 작업을 수행한다. 
                이 과정을 반복하여 파일의 모든 내용을 로컬에 저장한다.*/
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    private static String getFileName(String url) {
        return url.substring(url.lastIndexOf('/') + 1);
    }
}

- 자바를 이용한 (한번에 처리) 다운로드 방법2

서버에서 여러 파일을 압축하여 하나의 ZIP파일로 만들어 클라이언트로 저장하는 방식이다.
주로 파일 간의 관계가 있는 경우에 사용된다.

더보기
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipFileDownload {

    public static void main(String[] args) {
        //압축할 파일의 이름과 내용을 담은 리스트를 생성하고 초기화한다.
        List<String> fileNames = Arrays.asList("file1.txt", "file2.txt");
        List<String> fileContents = Arrays.asList("Content of file 1", "Content of file 2");

        /*압축된 ZIP 파일을 생성할 ZipOutputStream을 생성한다.
        그리고 try-with-resources 블록을 사용하여 자동으로 스트림을 닫는다. 
        생성된 ZIP 파일은 작성된 경로의 파일명으로 저장된다 */
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream("your_directory_path/files.zip"))) {
        
            //파일 이름과 내용의 리스트를 순회하면서 각 파일에 대한 압축 작업을 수행한다.
            for (int i = 0; i < fileNames.size(); i++) {
            
                //현재 처리 중인 파일에 대한 ZipEntry를 생성하고, ZipOutputStream에 추가한다.
                ZipEntry zipEntry = new ZipEntry(fileNames.get(i));
                zipOutputStream.putNextEntry(zipEntry);

                //현재 처리 중인 파일의 내용을 바이트 배열로 변환하고, ZipOutputStream을 통해 ZIP 파일에 쓰기를 수행한다.
                byte[] data = fileContents.get(i).getBytes();
                zipOutputStream.write(data, 0, data.length);

                //현재의 ZipEntry 처리를 마치고, 다음 ZipEntry로 넘어가기 전에 현재 엔트리를 닫는다.
                zipOutputStream.closeEntry();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}