Language/Java

파일을 복사하는 4가지 방법

이웃비 2022. 5. 16. 23:22

 

파일 이동을 위해 파일을 복사하려고 보니 가장 기본적인 I/O방법인 inputStream, outputStream에서 아파치 라이브러리를 사용하는 것까지 여러 가지 방법이 존재했다. 지금 프로젝트에선 FileInputStream을 사용하고 있지만, 다른 방법도 정리해보았다.

 

1. FileInputStream, FileOutputStream

가장 흔하고 많이 쓰이는 방법으로, FileInputStream으로 읽고 FileOutputStream으로 쓰는 방법이다

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class Main {

	public static void main(String[] args) {
		System.out.println("Start project!");
		
		// 1. A폴더, B폴더가 있는지 확인
		
		// 파일 객체 생성
		File fileA = new File("A");
		File fileB = new File("B");
		
		if(fileA.isDirectory() && fileB.isDirectory()) {
			
			// 2. A폴더에 파일이 있는지 확인 후
			File[] filesA =  fileA.listFiles();
			
			for(File oldFile : filesA){
				
				if(oldFile.isFile()){
					System.out.println("file name : "+ oldFile.getName());
					
					// 3. B폴더로 이동할 것이다.
					InputStream fin = null;
					OutputStream fout = null;
					
					byte[] b = new byte[1024];
					
					String dest = fileB.getPath() + "/" + oldFile.getName();
					System.out.println("destination : " + dest);
					
					File newFile = new File(dest);
					
					try {
						fin = new FileInputStream(oldFile);
						fout = new FileOutputStream(newFile);
						
						int read = 0;
						while((read = fin.read(b, 0, b.length)) != -1){
							fout.write(b, 0, read);
						}
						
					} catch (FileNotFoundException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					} finally {
						try {
							fin.close();
							fout.close();
							
							System.out.println("old file exists? : " + oldFile.exists());
							System.out.println("delete success? : " + oldFile.delete());

						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				} // if end
			} // for end
		}
		
		System.out.println("Done!");
	}
}

현재 프로젝트는 이 방법을 사용하고 있다. 메소드에 대한 설명은 여기를 참고하면 좋을 듯 하다.

 

2. Files.copy()

java.nio.file.Files클래스에서 제공하는 메소드로, 파일을 복사할 수 있다.

메소드

public static Path copy(Path source, Path target, CopyOption... options)

파라미터

source : 이동대상인 파일의 경로
target : 이동시킬 폴더의 파일 경로
options : 
  REPLACE_EXISTING : target파일이 이미 존재하면 덮어쓴다
  COPY_ATTRIBUTES : 파일 속성을 복사한다
  NOFOLLOW_LINKS : 파일이 symbolic link이면, 링크 대상이 아닌 symbolic link 자체가 복사된다. symbolic link = 바로가기

코드

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

public class FilesCopy {

	public static void main(String[] args) throws IOException {
		
		// A->B로 복사할 파일 준비
		File oldFile = new File("../moveFiles\\A\\file2.txt");
		File newFile = new File("../moveFiles\\B\\newFile2.txt");
		
		// 복사
		Files.copy(oldFile.toPath(), newFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
		
	}
}

만약 옵션에 REPLACE_EXISTING옵션을 주지 않고 거듭 실행하면 FileAlreadyExistsException이 발생한다.

 

3. FileChannel

FileChannel 클래스는 파일의 읽기, 쓰기, 맵핑 등을 위한 채널을 제공하며, transferFrom(), transferTo()메소드를 이용해 복사할 수 있다

메소드

public abstract long transferFrom(ReadableByteChannel src, long position, long count)
public abstract long transferTo(long position, long count, WritableByteChannel target)

파라미터

src : 파일채널
position : 복사를 시작할 위치
count : 복사할 데이터 사이즈

코드

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

public class FilesChannel {

	public static void main(String[] args) throws IOException {
		
		// 1.A->B로 복사할 파일 준비
		RandomAccessFile oldFile = new RandomAccessFile("../moveFiles\\A\\file2.txt", "r");
		RandomAccessFile newFile = new RandomAccessFile("../moveFiles\\B\\newFile2.txt", "rw");
		
		// 2.FileChannel 생성
		FileChannel source = oldFile.getChannel();
		FileChannel target = newFile.getChannel();
		
		// 3.복사
		source.transferTo(0, source.size(), target);
	
	}
}

이 메소드는 count만큼 복사하기 때문에 만일 target파일이 이미 존재하고, target파일의 데이터가 더 길면 source를 복사하고 남은 부분은 그대로 target에 남아있게 된다.

만일 

source파일 : "abcde"

target파일 : "fghijk"

이면, 복사 후 target파일은 "abcdek" 가 된다.

 

4. Apache Commons IO

Apache Common IO라이브러리를 사용하여 복사하는 방법이다.

클래스

FileUtils 

메소드

public static void copyFile(File srcFile, File destFile)

Apache Common IO라이브러리가 없으므로 lib폴더를 생성 후 다운받은 라이브러리를 빌드 패스에 추가시켰다.

코드

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

import org.apache.commons.io.FileUtils;

public class CopyFile {

	public static void main(String[] args) throws IOException {
		
		// 1.A->B로 복사할 파일 준비
		File oldFile = new File("../moveFiles\\A\\file2.txt");
		File newFile = new File("../moveFiles\\B\\newFile2.txt");
		
		// 2.복사
		FileUtils.copyFile(oldFile, newFile);
		
	}
}

 


 

그렇다면 JAVA IO와 NIO의 차이는 뭘까?

IO흐름

우선 자바IO는 NIO에 비해 느리다.

그 이유는 먼저 자바 IO는 커널 영역의 버퍼를 직접 건들지 못하고, 스트림 데이터가 처리되기 전까지 스트림을 사용하는 자바 스레드는 Blocking되기 때문이다.

예를들어, 파일읽기 작업시 커널에 명령을 전달하고, 커널은 시스템콜 (System Call) 을 사용해서 디스크 컨트롤러가 물리적 디스크로 부터 읽어온 파일 데이터를 커널 영역안의 버퍼로 저장한다. 그렇게 커널 안의 버퍼로 데이터가 저장되면 JVM 프로세스안의 버퍼로 복사를 시작하게 된다.

이때 프로세스로 버퍼를 복사하지 않고 직접 커널영역의 버퍼를 사용하면 훨씬 빠르게 복사할 수 있을 것이다.

이런 자바IO 의 단점을 JDK 1.4 부터 개선해서 나온것이 NIO (new IO) 이다.

NIO는 시스템 메모리를 직접 사용 할 수 있고(DirectByteBuffer) 채널을 통해 네이티브IO 를 사용할 수 있다.

특히 자바 NIO.2 (JDK 1.7 이상) 에서는 Files 클래스를 제공해서 Copy 메서드를 통해 파일복사를 할 수 있는데, 그냥 NIO를 통한 파일복사보다 훨씬 빠르다. 

그러니 자바 버전에 따라 적절한 방법을 사용하면 좋을 것 같다.

 

 


 

참고

https://creatordev.tistory.com/72

https://hianna.tistory.com/604