본문 바로가기
JAVA공부/0-자바기초(초급자)

[JAVA I/O]자바로 파일을 생성/저장하는 방법 / 4가지 방법 비교(FileChannel, FileWriter-BufferdWriter, BufferedOutputStream ,FileInputStream-FileOutputStream, Files)

by 으노으뇨 2022. 11. 15.
728x90
반응형
SMALL

안녕하세요~!

오늘은 자바기초 중에서 입출력관련되어서 공부한것을 메모해 보고자 합니다.

앞으로 개발일을 하거나 현업에서라던가 누군가 스쳐 지나가면서 물어볼때 

조금은! 도움되지 않을까 하는 마음으로 포스팅을 시작하겠습니다.


각 방법중 파일생성하는 방법에 대해서는 간단하게 소개형태로 진행하겠습니다.

상황 : 현재 웹에서 대용량파일(1.6gb)을 자바를 통해서 서버(로컬서버 또는 nfs)에 저장하는 기능을 개발하고자한다.

자바에서 파일을 저장/생성하는 5가지 방법

1. FileWriter와 BufferdWriter를 이용한 파일 생성

1-1. BufferedOutputStream 을 통한 파일 생성

2. FileInputStream-FileOutputStream

3. Files를 이용한 파일생성

4. FileChannel을 이용한 방법

그리고 FIle 객체를 MultipartFile객체로 변환하는 방법에 대한 소스도 작성했으니 유용하게 사용해주시면 감사하겠습니다.


학습성과 

파일 생성 방법의 효율성은 다양한 요인에 따라 달라집니다. 파일 크기, 종류, 작업 환경 등이 이에 해당합니다. 여기에는 각 생성 방법의 특성에 따른 효율성을 설명하겠습니다.

  1. 대용량 파일:
    • FileChannel을 이용한 방법Files를 이용한 파일 생성 방법이 대용량 파일에 대해 효율적일 수 있습니다. 이들은 NIO를 사용하며 비동기적으로 파일 I/O를 처리할 수 있습니다. 특히 FileChannel은 파일을 읽고 쓰는 데 더 효율적입니다.
  2. 작은 파일:
    • FileWriter와 BufferedWriter를 이용한 파일 생성이나 BufferedOutputStream을 통한 파일 생성은 작은 파일에 대해 유용할 수 있습니다. 이들은 작은 데이터를 버퍼에 쓴 후 한 번에 디스크에 쓰기 때문에 작은 파일 생성에 효율적입니다.
  3. 문서 파일:
    • 텍스트 기반의 문서 파일은 FileWriter와 BufferedWriter를 이용한 파일 생성이나 Files를 이용한 파일 생성과 같은 텍스트 파일 생성 방법을 사용하는 것이 좋습니다. 이들은 텍스트 데이터를 효율적으로 처리하고 버퍼링하여 디스크에 쓰기를 최적화합니다.
  4. 영상 및 바이너리 파일:
    • FileInputStream과 FileOutputStream을 이용한 방법이나 Files를 이용한 파일 생성은 바이너리 파일이나 영상과 같은 바이너리 데이터를 다룰 때 효과적입니다. 이들은 바이트 기반의 데이터를 처리하며 대용량 파일에도 적합합니다.

파일 생성 방법의 성능은 사용하는 시스템, 파일 크기, I/O 패턴 등에 따라 달라질 수 있습니다. 특정 작업에 가장 적합한 방법을 결정하기 위해서는 실제로 해당 방법을 사용하고, 각 방법에 대한 성능 측정을 진행하는 것이 좋습니다. 이를 통해 각 파일 유형과 용도에 최적화된 방법을 선택할 수 있습니다.



0. 웹에서 파일을 받아오는 것과 같은 환경셋팅

저는 제 전용 Java프로젝트에서 진행하겠습니다.

그러기 위해서는 웹단에서 대용량파일(1.53gb) 를 받아오는 것과 같은 환경으로 셋팅해야합니다.

준비단계에 대해 간략하게 진행하겠습니다.

로컬환경에서 1.6gb 파일 준비

용량 1.6gb의 iso 파일을 테스트파일로 진행하겠습니다.

*MultipartFile을 File 로 변환하는 방법은 아래글 참조해주세요

https://uno-kim.tistory.com/218

 

자바 파일 변환하기 / 자바 파일 컨버팅하기/ MultipartFile File 변환하기

안녕하세요~!! ㅎㅎㅎ 오랜만에 찾아 뵙겠습니다. 주말동안 큰일을 당하고 ... 주말 푹쉬지못하고... 기꺼이 정신 조금 차리고 글을 쓰게 되네요 ㅎㅎㅎ 이번엔 제가 최근에 혼자 개발하면서 공부

uno-kim.tistory.com

그리고 간단하게 메모리라던가 작동 시간을 체크하기 위해서 소스를 마지막으로 작성했습니다.

package ㅎJavaTests;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;

public class MemoryCheck
{
	public static void main(String[] args)
	{
		/**
		 * @메모 실행전 메모리 사용량 조회 > 파일 저장/생성 > 실행후 메모리 사용량 및 시간조회
		 **/
		System.out.println(":::::::: START FILE UPLOAD METHOD :::::::::");
		long prevTime = System.currentTimeMillis();
		System.gc();
		long beforeRunMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

		System.out.println("::: 시작전 나에게 부여된 메모리 : " + Runtime.getRuntime().maxMemory() + "	:::");
		System.out.println("::: 시작전 메모리		: " + beforeRunMemory + "	:::");
		/**
		 * @메모 파일 생성/저장 메서드호출
		 **/
		handleFile();
		System.gc();
		long afterRunMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
		System.out.println("::: 남아있는 메모리		: " + Runtime.getRuntime().freeMemory() + "	:::");
		System.out.println("::: 사용한 메모리		: " + (beforeRunMemory - afterRunMemory) + "	:::");
		long defTime = System.currentTimeMillis() - prevTime;
		System.out.println("::: 동작된 시간 		: " + defTime / 1000 + "초 " + defTime % 1000 + "		:::");
		System.out.println(":::::::: END METHOD :::::::::::::::::::::::");
	}
	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile()
	{
		/**
		 * 파일을 다루는 메서드이며 여기서 파일을 생성 및 저장
		 */
	}
}

이렇게 작성하고 돌려보면

이렇게 이쁘게 나옵니다. 이제 파일을 생성/저장하는 소스를 작성해보겠습니다.


1. FileWriter와 BufferdWriter를 이용한 파일 생성

우리가 가장많이 사용하는 방법이 아닐까 싶습니다.

주로 텍스트 TXT를 읽고 쓸때 사용됩니다. 

우선 저희가 테스트하는 파일형식인 iso와는 맞진않습니다. 그래도 시간측정을 위해 진행해보겠습니다.

	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";

		File myFile = new File(MY_LOCAL_TEST_FILE);
		File newFile = new File(NEW_FILE_PATH);
		// 복사 될 곳을 지정
		FileReader fr = new FileReader(myFile);
		FileWriter fw = new FileWriter(newFile);
		BufferedReader br = new BufferedReader(fr);
		BufferedWriter bw = new BufferedWriter(fw);
		int readBuffer = 0;
		char[] buffer = new char[512];
		// BufferedReader 로 부터 파일의 내용을 읽어와 저장한다.
		while ((readBuffer = br.read(buffer)) != -1)
		{
			bw.write(buffer, 0, readBuffer);
		}
	}

혹시 1.6기가가 넘는 파일을 처리할때는 얼마나 걸리는 지 확인해보겠습니다.

많이 오래걸렸군요

파일사이즈는 거의 1.5배 뿔었으며

파일이 깨졌네요 ㅎㅎㅎ


당연하겠지만 TXT를 주로 이용할때 사용하는 것으로 알려져있으며, Char[] 형태로 읽고 쓰다보니 파일이 깨질수 있다고 생각들었었습니다.

1-1 BufferedOutputStream 으로 생성

소스
	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";
		final File MY_FILE = new File(MY_LOCAL_TEST_FILE);
		final File NEW_FILE = new File(NEW_FILE_PATH);

		// 복사 될 곳을 지정
		FileInputStream fis = new FileInputStream(MY_FILE);
		FileOutputStream fos = new FileOutputStream(NEW_FILE);
		BufferedInputStream bis = new BufferedInputStream(fis);
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		int bytesData = 0;
		byte[] bufferArr = new byte[4096];
		// BufferedReader 로 부터 파일의 내용을 읽어와 저장한다.
		while ((bytesData = bis.read(bufferArr)) != -1)
		{
			bos.write(bufferArr, 0, bytesData);
		}
	}
결과

시간 : 88초 용량, 파일 이상없음


2. FileInputStream-FileOutputStream을 이용한 생성

그리고 두번째는 우리가 인터넷에서 많이접하는 방법입니다.

	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";

		final File myFile = new File(MY_LOCAL_TEST_FILE);
		final File newFile = new File(NEW_FILE_PATH);
		// 복사 될 곳을 지정
		FileInputStream fis = new FileInputStream(myFile);
		FileOutputStream fos = new FileOutputStream(newFile);
		int bytesData = 0;
		byte[] bufferArr = new byte[4096];
		// BufferedReader 로 부터 파일의 내용을 읽어와 저장한다.
		while ((bytesData = fis.read(bufferArr)) != -1)
		{
			fos.write(bufferArr, 0, bytesData);
		}
	}

특징은 바이트화하여 객체를 생성합니다. 실행해보겠습니다.

다음은 결과입니다.

시간 : 67초, 용량, 파일깨짐 이상없음


3. Files를 이용한 파일생성

자바의 java.nio 클래스를 들여다보면 Files가 있습니다.

이 방법으로 3가지 방법으로 복사를 통한 파일생성하는 방법으로 확인해보겠습니다.


3-1 Files.Copy(InputStream input, Path path)

	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";
		final File MY_FILE = new File(MY_LOCAL_TEST_FILE);
		final File NEW_FILE = new File(NEW_FILE_PATH);

		FileInputStream fis = new FileInputStream(MY_FILE);
		/**
		 * @author zhfld
		 * @메모 : copy()의 파라미터내부에는 InputStream, 새로생성될파일의 경로이렇게 입력한다.
		 * @Description 입력 스트림에서 파일로 모든 바이트를 복사합니다.
		 **/
		Files.copy(fis, NEW_FILE.toPath());
	}

결과

시간 : 86초 , 용량파일 이상없음


3-2 Files.Copy(Path path, OutputStream out)

소스

	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";
		final File MY_FILE = new File(MY_LOCAL_TEST_FILE);
		final File NEW_FILE = new File(NEW_FILE_PATH);

		FileOutputStream fos = new FileOutputStream(NEW_FILE);
		/**
		 * @author zhfld
		 * @메모 : copy()의 파라미터내부에는 OutputStream, 복사할파일 경로이렇게 입력한다.
		 **/
		Files.copy(MY_FILE.toPath(), fos);
	}

58초, 용량파일 이상없음


 

3-3 Files.copy(Path path, Path path , Option ot)

소스

현재 옵션은 StandardCopyOption.REPLACE_EXISTING 로 지정

	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";
		final File MY_FILE = new File(MY_LOCAL_TEST_FILE);
		final File NEW_FILE = new File(NEW_FILE_PATH);
		/**
		 * @author zhfld
		 * @param :
		 *            첫번째 파라미터에는 복사할 대상
		 * @param :
		 *            두번째 파라미터에는 복사될 타겟(생성될 위치나 그런것들 정보)
		 * @param :
		 *            StandardCopyOption의 이넘타입으로 부터받는다. 
		 *            --REPLACE_EXISTING : 기존에 파일있으면 교체 
		 *            --COPY_ATTRIBUTES : 파일의 속성 복사
		 **/
		Files.copy(MY_FILE.toPath(), NEW_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING);
	}

결과

객체 생성시간을 보면 9월 4일 오후 6시 이다. 그냥 복붙한것과같은 느낌이다.

9초 , 용량파일 이상없음


옵션변경후 재시도

옵션 : StandardCopyOption.COPY_ATTRIBUTES

16초, 이상없음

흠 이상하다.. 우선 진짜 복사하는것같다. Path to Path에 대해서는 한번 봐야겠다.....


4. FileChannel을 이용한 방법

소스

	/**
	 * @메모 파일 생성/저장 메서드호출
	 **/
	static void handleFile() throws IOException
	{
		final String MY_LOCAL_TEST_FILE = "D:\\FileTest\\BigSizeVolumeFile(1.53gb).iso";
		final String NEW_FILE_PATH = "D:\\FileTest\\saveFile.iso";
		final File MY_FILE = new File(MY_LOCAL_TEST_FILE);
		final File NEW_FILE = new File(NEW_FILE_PATH);

		FileChannel fcMyFile = FileChannel.open(MY_FILE.toPath(), StandardOpenOption.READ);
		FileChannel fcNewFile = FileChannel.open(NEW_FILE.toPath()
        , StandardOpenOption.CREATE
        , StandardOpenOption.WRITE);
		ByteBuffer buffer = ByteBuffer.allocateDirect(100);
		int byteCount;
		while (true)
		{
			buffer.clear();
			byteCount = fcMyFile.read(buffer);
			if(byteCount == -1) break;
			buffer.flip();
			fcNewFile.write(buffer);
		}
	}

결과

시간 : 223초 용량파일 이상없음


이것으로 파일 저장/생성하는 소스를 알아보았습니다.

 

 

참고로 시간은 정확하지 않습니다. OS에서 파일을 관리하고 저장하는 자원을 사용하므로 그때 그때 할당된 자원이라던지 등등에 따라서 천차만별로 제 로컬에서 제어됩니다. 따라서 40~100초 사이에서 걸린 방법에 대해서는 큰 차이가 없음을 강조드립니다.... 나중에 CPU 사용량까지 모니터링하면서 다시 작성하겠습니다..

읽고쓰는 경우 CPU도 체크하면서 하고싶었는데 왜

OperatingSystemMXBean

를 사용할 수없는건지... (제 로컬에서는) 

그래서 나중에 라이브러리라던가 그런것을 넣고 사용한다면 좋을것같습니다.

- 아물론 해당 클래스를 사용할 수 있지만 CPU를 전반적으로 용량을 체크하고 그런 메서드는 없더라구요..ㅠㅠ

결과입니다.

BufferedOutputStream : 88초

*FileInputStream-FileOutputStream : 66초

Files.Copy(InputStream input, Path path) : 86초

*Files.Copy(Path path, OutputStream out) : 58초

*FileChannel : 223초

결과가 정확하지 않을 수 있지만 그래도 소스를 기록하면서 얼마나 걸리는지 대략적으로 측정도 해봤습니다.

어디까지나 소스 메모용입니다. 시간측정은 약간의 재미로만 봐주세요

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


그리고 번외편으로 버퍼트아웃풋스트림이나 파일스트림등을 사용할때 우리는 바이트를 4096를 넣어서 사용했습니다.

요런식으로?

이제 요 부분에서 new byte[4096]을 사진과같이 9999로 했을때와 1로 줬을때 차이가 있는지 볼게요

 

테스트할 상황은 FileInputStream / FileOutputStream 객체를 사용할때이다.

1. 9999 입력

2. 1 입력

 

결과 : 둘다 잘된다.

728x90
반응형
LIST

댓글