1. 程式人生 > 程式設計 >java多種檔案複製方式以及效率比較

java多種檔案複製方式以及效率比較

1.背景

java複製檔案的方式其實有很多種,可以分為

  • 傳統的位元組流讀寫複製FileInputStream,FileOutputStream,BufferedInputStream,BufferedOutputStream
  • 傳統的字元流讀寫複製FileReader,FileWriter,BufferWriter,BufferedWriter,BufferedReader
  • NIO系列的FileChannel
  • FileChannel+緩衝
  • java.nio.Files.copy()
  • 第三方包中的FileUtils.copy方法,比如org.apache.commons.io.FileUtils,org.codehaus.plexus.util.FileUtils等等.

所以呢,看看各種方法效率怎麼樣,主要衡量的標準就是時間,另外的一些標準包括大檔案的複製時的記憶體溢位等問題.

2.概述

由於很多時候複製檔案都包括了資料夾下的所有子目錄及檔案的複製,所以作者採用的遍歷+複製方法去複製檔案.就是把整個複製過程分為先遍歷,遍歷的過程中遇到資料夾就建立,遇到檔案就呼叫不同的複製方法. 遍歷的5種方法:

  • (1)File.listFiles()
  • (2)File.list()
  • (3)org.codehaus.plexus.util.FileUtils.getFiles()
  • (4)org.apache.commons.io.FileUtils.listFiles()
  • (5)java nio中的java.nio.file.Files.walkFileTree

複製的8種方法:

  • (1)FileInputStream+FileOutputStream
  • (2)BufferedInputStream+BufferedOutputStream
  • (3)FileReader+FileWriter
  • (4)BufferedReader+BufferedWriter
  • (5)FileChannel
  • (6)FileChannel+buffer
  • (7)org.apache.commons.io.FileUtils.copyFile()
  • (8)java.nio.file.Files.copy()

另外作者不太想看控制檯.....所以配合了一點swing使用.

3.jar包

1.org.apache.commons 2.org.codehaus.plexus

4.遍歷

(1)listFiles()

 private static void traverseByListFiles(File srcFile,File desFile) throws IOException
{
	if(srcFile.isDirectory())
	{
		File[] files = srcFile.listFiles();
		assert files != null;
		for(File file : files)
		{
			File desFileOrDir = new File(desFile.getAbsolutePath() + File.separator + file.getName());
			if(file.isDirectory())
			{
				if(desFileOrDir.exists())
					desFileOrDir.delete();
				desFileOrDir.mkdirs();
			}
			traverseByListFiles(file,desFileOrDir);
		}
	}
	else 
	{
		copyFile(srcFile,desFile);
	}
}
複製程式碼

通過srcFile的listFiles()獲取所有的子檔案與子資料夾,然後判斷是否是目錄 如果是目錄,首先判斷有沒有這個檔案(有時候本來是資料夾但是卻存在同名的檔案,就先刪除),再建立資料夾,然後遞迴執行函式. 如果不是目錄,直接把兩個File作為引數進行檔案複製,裡面用什麼方法後面會設定.

(2)list()

private static void traverseByList(File srcFile,File desFile) throws IOException
{
	if (srcFile.isDirectory())
	{
		String[] files = srcFile.list();
		assert files != null;
		for (String file : files)
		{
			File subSrcFile = new File(srcFile,file);
			File subDesFile = new File(desFile,file);
			if (subSrcFile.isDirectory())
			{
				if (subDesFile.exists())
					subDesFile.delete();
				subDesFile.mkdirs();
			}
			traverseByList(subSrcFile,subDesFile);
		}
	}
	else
	{
		copyFile(srcFile,desFile);
	}
}
複製程式碼

list與第一種listFiles()類似,不過是String[],也是先判斷目錄,建立目錄,不是目錄直接複製

(3)org.codehaus.plexus.util.FileUtils.getFiles

private static void traverseByGetFiles(File srcFile,File desFile) throws IOException
{
	if (srcFile.isDirectory())
	{
		java.util.List<File> fileList = org.codehaus.plexus.util.FileUtils.getFiles(srcFile,null,null);
		for (File file : fileList)
		{
			File desFileOrDir = new File(desFile.getAbsolutePath() + File.separator + file.getName());
			if(file.isDirectory())
			{
				if(desFileOrDir.exists())
					desFileOrDir.delete();
				desFileOrDir.mkdirs();
			}
			traverseByListFiles(file,desFileOrDir);
		}
	}
	else
	{
		copyFile(srcFile,desFile);
	}
}
複製程式碼

這是用了別人的工具類進行遍歷.

org.codehaus.plexus.util.FileUtils.getFiles(srcFile,null);
複製程式碼

返回的結果的java.util.List

(4)Commons.io工具包

private static void traverseByCommonsIO(File srcFile,File desFile) throws IOException
{
	if (srcFile.isDirectory())
	{
		Collection<File> files = org.apache.commons.io.FileUtils.listFiles(srcFile,false);
		for (File file : files)
		{
			File desFileOrDir = new File(desFile.getAbsolutePath() + File.separator + file.getName());
			if(file.isDirectory())
			{
				if(desFileOrDir.exists())
					desFileOrDir.delete();
				desFileOrDir.mkdirs();
			}
			traverseByCommonsIO(file,desFileOrDir);
        }
	}
	else 
	{
		copyFile(srcFile,desFile);
	}
}
複製程式碼

使用org.apache.commons.io.FileUtils的listFiles方法,引數為要遍歷的目錄,一個null和一個false,第二個引數表示過濾器,表示過濾出特定字尾名的檔案,型別為String [],第三個布林引數表示是否遞迴訪問子目錄.

(5)NIO--walkFileTree

利用FileVisitor這個介面.實際中常用SimpleFileVisitor.

private static void traverseByNIO2(File srcFile) throws IOException
{
	java.nio.file.Files.walkFileTree(srcFile.toPath(),new SimpleFileVisitor<>() {
		@Override
		public FileVisitResult visitFile(Path path,BasicFileAttributes attrs) throws IOException 
		{
			File d = new File(des.toString() + path.toAbsolutePath().toString().substring(src.toString().length()));
            new File(d.toString().substring(0,d.toString().lastIndexOf(File.separator))).mkdirs();
            copyFile(path.toFile(),d);
            return FileVisitResult.CONTINUE;
        }
    });
}
複製程式碼

FileVisitor介面定義了四個方法,分別為:

public interface FileVisitor<T>
{
	FileVisitResult preVisitDirectory(T dir,BasicFileAttributes attrs)
	{
		//訪問dir前的操作,dir型別一般為java.nio.Path
	}
	
	FileVisitResult postVisitDirectory(T dir,BasicFileAttributes attrs)
	{
		//訪問dir後的操作
	}
	
	FileVisitResult visitFile(T file,BasicFileAttributes attrs)
	{
		//訪問file時的操作
	}
	
	FileVisitResult visitFileFailed(T file,BasicFileAttributes attrs)
	{
		//訪問file失敗時的操作
	}
}
複製程式碼

在上面的例子中只是實現了visitFile,因為只是複製操作,首先判斷是否是源目錄的路徑,不是的話建立資料夾再複製檔案. 這裡說一下返回值FileVisitResult.FileVisitResult是一個列舉型別,根據返回值判斷是否繼續遍歷. FileVisitResult可取值:

  • CONTINUE:繼續
  • TERMINNATE:結束
  • SKIP_SIBLINGS:繼續,跳過同一目錄的節點
  • SKIP_SUBTREE:繼續,跳過子目錄,但會訪問子檔案

5.複製

(1)FileInputStream+FileOutputStream

首先是經典的位元組流FileInputStream+FileOutputStream,這個比較簡單,使用FileInputStream讀取後使用FileOutputStream寫入,不過效率嘛.....一般般.

private static void copyByFileStream(File srcFile,File desFile) throws IOException
{
	FileInputStream inputStream = new FileInputStream(srcFile);
	FileOutputStream outputStream = new FileOutputStream(desFile);
	byte [] b = new byte[1024];
	while(inputStream.read(b) != -1)
	{
		outputStream.write(b);
		addCopySize();
	}
	inputStream.close();
	outputStream.close();
}
複製程式碼

這裡說一下三個read方法的區別,FileInputStream有三個read方法:

input.read();
input.read(b);
input.read(b,off,len);
複製程式碼

A.read()

逐個位元組進行讀取,返回int,寫入時直接使用write(n);

int n = input.read();
output.write(n);
複製程式碼

這個可以說是三個read中最慢的....作者試了一個2G左右的檔案,用了大概10分鐘才複製160M......

B.read(b)

引數是一個byte [],將位元組緩衝到其中,返回陣列的位元組個數,這個比read()快很多.

byte [] b = new byte[1024];
while(input.read(b) != -1)
	output.write(b);
複製程式碼

C.read(b,len)

這個方法其實和read(b)差不多,read(b)相當於省略了引數的read(b,len).

byte [] b = new byte[1024];
int n;
while((n = input.read(b,0,1024))!=-1)
	output.write(b,n);
複製程式碼
public int read(byte b[],int off,int len) throws IOException 
{
	return readBytes(b,len);
}

public int read(byte b[]) throws IOException 
{
	return readBytes(b,b.length);
}
複製程式碼

這兩個都是呼叫一樣的readBytes():

private native int readBytes(byte b[],int len) throws IOException;
複製程式碼

至於效率...可以看看結果(作者用的是10G內的小檔案):

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
可以看到,沒有哪個一定比另外一個更快(不過最後一個誤差有點太大了?7G不夠的檔案.). 採用哪一個建議自己去測試,畢竟這存在很多誤差,比如檔案,java版本,機器本身等等,僅供參考.

(2)BufferedInputStream+BufferedOutputStream

緩衝位元組流BufferedInputStream+BufferedOutputStream,相比起FileInputStream,BufferedInputStream讀取時會先從緩衝區讀取資料,緩衝區無可讀資料再從檔案讀取,所以會比FileInputStream快.

private static void copyByBufferStream(File srcFile,File desFile) throws IOException
{
	BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile));
	BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(desFile));
	byte [] b = new byte[1024];
	while(inputStream.read(b) != -1)
	{
		addCopySize();
		outputStream.write(b);
	}
	inputStream.close();
	outputStream.close();
}
複製程式碼

這裡也說一下BufferedInputStream的三個read(實際上還有,還有readN,與read(),read()肯定最慢,readN作者很少用,所以就沒列出來了)

read(b);
read(b,len);
readAllBytes();
複製程式碼

A.read(b)

這個其實和FileInputStream的那個沒啥區別,把一個位元組陣列仍進去就好了.

B.read(b,len)

這個....也和FileInputStream那個沒啥區別,不說了

C.readAllBytes()

這個一次可以讀取所有的位元組.不過用這個雖然省事,可以直接

output.write(input.readAllBytes());
複製程式碼

但是呢,有代價的:

在這裡插入圖片描述
會出現OutOfMemory錯誤,就是對於大檔案還是老老實實分開吧,不要"一口搞定","多吃幾口".

看看效率:

在這裡插入圖片描述
readAllBytes對於大檔案(作者這個是5G內的檔案)直接爆記憶體....
在這裡插入圖片描述
在這裡插入圖片描述
readAllBytes()又爆了.....這個才2G不到的檔案...readAllBytes()看來不是很給力啊....不過對於小檔案效率還可以接受.

(3)FileReader+FileWriter

字元流讀寫FileReader+FileWriter,相比起位元組流的read,基本上把byte[]換成char[]即可,因為是逐個字元讀取,而位元組流是逐個位元組讀取因此採用byte[]. 注意這個不能用來讀取圖片,音樂等檔案,不然複製出來的檔案打不開.

private static void copyByFileReader(File srcFile,File desFile) throws IOException
{
	FileReader reader = new FileReader(srcFile);
	FileWriter writer = new FileWriter(desFile);

	char [] c = new char[1024];
	while(reader.read(c) != -1)
	{
		addCopySize();
		writer.write(c);
	}
	reader.close();
	writer.close();
}
複製程式碼

(4)BufferedReader+BufferedWriter

緩衝字元流讀寫BufferedReader+BufferedWriter,BufferedReader相比起FileReader有一個readLine()方法,可以每行讀入,會比FileReader快.對應的BufferedWriter提供了write(String)方法,當然也有write(String s,int off,int len).同樣這個不能用來讀取圖片等.

private static void copyByBufferReader(File srcFile,File desFile) throws IOException
{
	BufferedReader reader = new BufferedReader(new FileReader(srcFile));
	BufferedWriter writer = new BufferedWriter(new FileWriter(desFile));

	char [] c = new char[1024];
	while(reader.read(c) != -1)
	{
		addCopySize();
		writer.write(c);
	}
	reader.close();
	writer.close();
}
複製程式碼

(5)NIO--FileChannel

通過FileChannel複製,首先通過FileInputStream與FileOutputStream開啟流,再用getChannel()方法.最後使用transferTo()或transferFrom()進行復制,一條語句即可,十分方便,而且效率很高.

private static void copyByFileChannel(File srcFile,File desFile) throws IOException
{
	FileChannel srcChannel = new FileInputStream(srcFile).getChannel();
	FileChannel desChannel = new FileOutputStream(desFile).getChannel();
	srcChannel.transferTo(0,srcChannel.size(),desChannel);
	srcChannel.close();
	desChannel.close();
}
複製程式碼

(6)NIO--FileChannel+ByteBuffer

在利用了FileInputStream與FileOutputStream開啟了FileChannel的基礎上,配合ByteBuffer使用.

private static void copyByFileChannelWithBuffer(File srcFile,File desFile) throws IOException
{
	FileChannel srcChannel = new FileInputStream(srcFile).getChannel();
	FileChannel desChannel = new FileOutputStream(desFile).getChannel();
	ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
	while(srcChannel.read(buffer) != -1)
	{
		buffer.flip();
		desChannel.write(buffer);
		buffer.clear();
		addCopySize();
	}
	srcChannel.close();
	desChannel.close();
}
複製程式碼

flip的意思是"翻轉",

buffer.flip();
複製程式碼

把Buffer從寫模式變為讀模式,接著write(buffer),再把buffer清空. 看看這兩種方法效率:

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

另外作者發現transferTo的"上限"為2G,就是對於大於2G的單個檔案最多最能複製2個G. 所以...對於大檔案沒有可比性了.

(7)FileUtils.copyFile()

這是工具類,沒啥好說的,引數是兩個File,分別表示源與目標.

private static void copyByCommonsIO(File srcFile,File desFile) throws IOException
{
	FileUtils.copyFile(srcFile,desFile);
}
複製程式碼

(8)Files.copy()

這是官方提供的Files工具類,前兩個引數為Path,分別表示源與目標,可以設定第三個引數(或者省略),表示選項.例如可以設定

StandardCopyOption.REPLACE_EXISTING
複製程式碼
private static void copyByFiles(File srcFile,File desFile) throws IOException
{
	Files.copy(srcFile.toPath(),desFile.toPath(),StandardCopyOption.REPLACE_EXISTING);
}
複製程式碼

注意Files.copy會保持檔案的隱藏屬性,原來是隱藏的檔案複製後也是隱藏的.以上7種則不會.

6.其他

(1)swing佈局

A.網格佈局

主JFrame採用了網格佈局

setLayout(new GridLayout(3,1,5,3));
複製程式碼

三行一列,因為只要三個按鈕,選擇原始檔(夾),選擇目標資料夾,選擇遍歷方式. 選擇遍歷方式/複製方式的JFrame同樣適用了網格佈局:

showTraverseMethod.setLayout(new GridLayout(5,3,3));
showCopyMethod.setLayout(new GridLayout(4,2,5));
複製程式碼

B.居中

setBounds(
(int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,(int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - 200,400,400);
複製程式碼

高400,寬400,利用ToolKit.getDefaultToolKit().getScreenSize()獲取螢幕的高度和寬度實現居中.

C.元件的新增與刪除

由於在主JFrame中只有三個按鈕,選擇完遍歷方式後需要更新這個元件,作者的做法是先刪除這個元件在新增元件:

traverseMethodButton.setVisible(false);
remove(traverseMethodButton);
add(copyMethodButton);
copyMethodButton.setVisible(true);
複製程式碼

設定它不可見再刪除,再新增另一元件,再設定可見.

(2)進度條

進度條這個東西把作者搞得很慘啊......其實就是新建一個執行緒就可以了. 核心程式碼為:

new Thread(
	() ->
	{
		int percent;
		while ((percent = getCopyPercent()) < 100)
		{
			try
			{
				Thread.sleep(100);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			copyProgressBar.setValue(percent);
		}
	}
).start();
複製程式碼

作者的JProgressBar是直接新增在一個JFrame中的,不用什麼太複雜的佈局. 獲取百分比後呼叫setValue(),一定要新建一個執行緒操作,不然不能正常顯示進度條. 另外複製的操作建議使用SwingWorker.

SwingWorker<String,Object> copyTask = new SwingWorker<>()
{
	@Override
	protected String doInBackground()
	{
		try
		{
			if (traverseMethod[0])
				traverseByListFiles(src,des);
			else if (traverseMethod[1])
				traverseByList(src,des);
			else if (traverseMethod[2])
				traverseByGetFiles(src,des);
			else if (traverseMethod[3])
				traverseByCommonsIO(src,des);
			else if (traverseMethod[4])
				traverseByNIO2(src);
			else
			{
				showProgressBar.dispose();
				showMessage("遍歷失敗,找不到遍歷方法");
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
			showProgressBar.dispose();
			showMessage("未知錯誤複製失敗");
		}
		finish(start);
		return null;
	}
};
copyTask.execute();
複製程式碼

7.測試

說了那麼多來點實際的. (以下所有的測試都是刪除複製的檔案後再進行新一次的複製.)

(1)1G檔案

1G file File.listFiles() File.list() plexus.util.FileUtils.getFiles() commons.io.FileUtils.listFiles Files.walkFileTree
FileIntput/OutputStream 20.189s 21.152s 18.249s 20.131s 21.782s
BufferedInput/OuputStream 17.761s 23.786s 22.118s 19.646s 16.806s
FileReader/Writer 61.334s 58.3s 58.904s 58.679s 55.762s
BufferedReader/Writer 63.287s 59.546s 56.664s 58.212s 59.884s
FileChannel 20.097s 22.272s 22.751s 22.765s 20.291s
FileChannel+ByteBuffer 18.857s 22.489s 23.148s 22.337s 17.213s
FileUtils.copyFile 25.398s 21.95s 22.808s 25.325s 22.483s
Files.copy 16.272s 14.166s 17.057s 14.987s 10.653s

檔案的話其實縱向比較即可,因為基本不用怎麼遍歷,橫向比較可以勉強看作求平均值. 對於非文字檔案,FileReader/Writer和BufferedReader/Writer沒有太大的參考意義,比如複製視訊檔案是打不開的,而且複製出來的檔案會變大.對於單檔案Files.copy的效能非常好,java的nio果然厲害.

(2)10G檔案

10G file File.listFiles() File.list() plexus.util.FileUtils.getFiles() commons.io.FileUtils.listFiles Files.walkFileTree
FileIntput/OutputStream 171.427s 173.146s 172.611s 184.182s 250.251s
BufferedInput/OuputStream 203.509s 174.792s 167.727s 177.451s 217.53s
FileReader/Writer 187.55s 169.306s 226.571s 168.982s 218.303s
BufferedReader/Writer 155.134s 165.883s 166.192s 176.488s 206.306s
FileChannel 34.48s 35.445s 43.896s 41.827s 41.755s
FileChannel+ByteBuffer 175.632s 167.091s 178.455s 182.977s 183.763s
FileUtils.copyFile 203.997s 206.623s 201.01s 213.949s 208.739s
Files.copy 209.898s 186.889s 244.355s 222.336s 244.68s

這個10G的檔案是文字檔案. 現在可以看看FileChannel的這一行,明顯所花的時間要比其他要少,為什麼呢? 因為檔案大於2G.FileChannel的trasferTo方法只能寫入最多2G的檔案,所以對於大於2G的檔案複製出來只有2G,因此FileChannel的這一行沒有太大可比性.對於文字檔案,BufferedReader/Writer的複製速度是最快的了,其次是FileInput/OutputStream.對於單個大檔案,apache的FileUtils與NIO的Files.copy的速度比FileInputStream慢啊...

(3)1G目錄

1G dir File.listFiles() File.list() plexus.util.FileUtils.getFiles() commons.io.FileUtils.listFiles Files.walkFileTree
FileIntput/OutputStream 23.549s 99.386s 143.388s 13.451s 10.773s
BufferedInput/OuputStream 6.306s 59.458s 20.704s 6.668s 6.616s
FileReader/Writer 49.059s 103.257s 51.995s 49.729s 51.509s
BufferedReader/Writer 59.932s 127.359s 51.731s 51.418s 50.317s
FileChannel 40.082s 71.713s 17.617s 15.782s 19.777s
FileChannel+ByteBuffer 33.355s 83.845s 19.68s 10.288s 17.152s
FileUtils.copyFile 24.163s 63.979s 8.277s 6.115s 19.513s
Files.copy 14.528s 28.215s 6.578s 5.883s 7.502s

對於目錄的話可以考慮放棄BufferedReader與FileReader了,除非全部是文字檔案,否則推薦使用BufferedInput/OutputStream與Files.copy()進行復制,工具類FileUtils的複製方法表現還是不錯的,但相比起java標準的Files.copy效率都差了. 對於FileChannel與配合緩衝使用的FileChannel,1G的話好像不相上下. 遍歷方式的話...可以看到plexus的遍歷方法表現差距很大,而apache的listFiles或者java nio的walkFileTree比較穩定且速度還可以,推薦使用這兩種方式遍歷目錄.

(4)10G目錄

10G dir File.listFiles() File.list() plexus.util.FileUtils.getFiles() commons.io.FileUtils.listFiles Files.walkFileTree
FileIntput/OutputStream 216.822s 228.792s 227.908s 240.042s 191.863s
BufferedInput/OuputStream 218.599s 210.941s 207.375s 213.991s 167.614s
FileReader/Writer 536.747s 550.755s 550.415s 548.881s 516.684s
BufferedReader/Writer 587.612s 552.55s 549.716s 553.484s 498.18s
FileChannel 115.126s 117.538s 117.456s 118.207s 97.626s
FileChannel+ByteBuffer 225.887s 224.932s 222.077s 223.812s 180.177s
FileUtils.copyFile 233.724s 230.199s 232.133s 223.286s 189.737s
Files.copy 229.819s 227.562s 226.793s 226.78s 181.071s

FileReader與BufferedReader這兩行可以忽略了.對於小檔案用FileChannel的話還是不錯的,對於大檔案一定要用FileChannel的話可以配合ByteBuffer使用,不過從資料上看效果比BufferedInput/OutputStream要低. 再看看org.apache.commons.io.FileUtils與java.nio.file.Files的copy,差別不太,效果接近,但在1G的時候差距有點大. 遍歷方式的話,java nio的walkFileTrees最快.

當然這些測試僅供參考,具體使用哪一個要看看具體環境,另外這種方式把遍歷與複製分開,apache的FileUtils有方法可以直接複製目錄的,因此,使用哪個更合適還需要個人具體測試.

8.原始碼

作者比較偷懶全部仍在一個檔案了.七百行.

import java.awt.*;
import javax.swing.*;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;
import org.apache.commons.io.*;

public class Test extends JFrame
{
    public static final long serialVersionUID = 12398129389122L;

    private JFrame showTraverseMethod = new JFrame("遍歷方式");
    private JFrame showCopyMethod = new JFrame("複製方式");

    private JButton traverseMethodButton = new JButton("請選擇遍歷方式");
    private JButton copyMethodButton = new JButton("請選擇複製方式");
    private JButton copyButton = new JButton("開始複製");

    private JButton traverseByListFiles = new JButton("File.listFiles()");
    private JButton traverseByList = new JButton("File.list()");
    private JButton traverseByGetFiles = new JButton("(plexus)getFiles()");
    private JButton traverseByCommonsIO = new JButton("Commons IO");
    private JButton traverseByNIO2 = new JButton("NIO2");

    private JButton copyByFileStream = new JButton("File stream");
    private JButton copyByBufferStream = new JButton("Buffer stream");
    private JButton copyByFileReader = new JButton("File reader");
    private JButton copyByBufferReader = new JButton("Buffer reader");
    private JButton copyByFileChannel = new JButton("File channel");
    private JButton copyByFileChannelWithBuffer = new JButton("File channel with buffer");
    private JButton copyByCommonsIO = new JButton("Commons IO");
    private JButton copyByFiles = new JButton("Files.copy");

    public Test()
    {
        JButton src = new JButton("選擇原始檔(夾)");
        src.addActionListener(
            event ->
            {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
                fileChooser.showDialog(new Label(),"選擇檔案(夾)");
                FilesCopy.setSrc(fileChooser.getSelectedFile());
            }
        );
        JButton des = new JButton("選擇目標資料夾");
        des.addActionListener(
            event ->
            {
                JFileChooser fileChooser = new JFileChooser();
                fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
                fileChooser.showDialog(new JLabel(),"選擇資料夾");
                FilesCopy.setDes(fileChooser.getSelectedFile());
            }
        );

        traverseMethodButton.addActionListener(
            event ->
            {
                traverseByListFiles.addActionListener(
                    e->
                    {
                        FilesCopy.setTraverseByListFiles();
                        showTraverseMethod.dispose();
                    }
                );

                traverseByList.addActionListener(
                    e ->
                    {
                        FilesCopy.setTraverseByList();
                        showTraverseMethod.dispose();
                    }
                );

                traverseByGetFiles.addActionListener(
                    e ->
                    {
                        FilesCopy.setTraverseByGetfiles();
                        showTraverseMethod.dispose();
                    }
                );

                traverseByCommonsIO.addActionListener(
                    e ->
                    {
                        FilesCopy.setTraverseByCommonsIO();
                        showTraverseMethod.dispose();
                    }
                );

                traverseByNIO2.addActionListener(
                    e ->
                    {
                        FilesCopy.setTraverseByNIO2();
                        showTraverseMethod.dispose();
                    }
                );


                showTraverseMethod.setLayout(new GridLayout(5,3));
                showTraverseMethod.setTitle("遍歷方式");
                showTraverseMethod.setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,400);
                showTraverseMethod.setVisible(true);
                showTraverseMethod.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                showTraverseMethod.add(traverseByListFiles);
                showTraverseMethod.add(traverseByList);
                showTraverseMethod.add(traverseByGetFiles);
                showTraverseMethod.add(traverseByCommonsIO);
                showTraverseMethod.add(traverseByNIO2);

                traverseMethodButton.setVisible(false);
                remove(traverseMethodButton);
                add(copyMethodButton);
                copyMethodButton.setVisible(true);
            }
        );

        copyMethodButton.addActionListener(
            event ->
            {
                copyByFileStream.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByFileStream();
                        showCopyMethod.dispose();
                    }
                );

                copyByBufferStream.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByBufferStream();
                        showCopyMethod.dispose();
                    }
                );

                copyByFileReader.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByFileReader();
                        showCopyMethod.dispose();
                    }
                );

                copyByBufferReader.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByBufferReader();
                        showCopyMethod.dispose();
                    }
                );

                copyByFileChannel.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByFileChannel();
                        showCopyMethod.dispose();
                    }
                );

                copyByFileChannelWithBuffer.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByFileChannelWithBuffer();
                        showCopyMethod.dispose();
                    }
                );

                copyByCommonsIO.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByCommonsIO();
                        showCopyMethod.dispose();
                    }
                );

                copyByFiles.addActionListener(
                    e ->
                    {
                        FilesCopy.setCopyByFiles();
                        showCopyMethod.dispose();
                    }
                );

                showCopyMethod.setLayout(new GridLayout(4,5));
                showCopyMethod.setTitle("複製方式");
                showCopyMethod.setBounds(
                        (int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,400);
                showCopyMethod.setVisible(true);
                showCopyMethod.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                showCopyMethod.add(copyByFileStream);
                showCopyMethod.add(copyByBufferStream);
                showCopyMethod.add(copyByFileReader);
                showCopyMethod.add(copyByBufferReader);
                showCopyMethod.add(copyByFileChannel);
                showCopyMethod.add(copyByFileChannelWithBuffer);
                showCopyMethod.add(copyByCommonsIO);
                showCopyMethod.add(copyByFiles);

                copyMethodButton.setVisible(false);
                remove(copyMethodButton);
                add(copyButton);
                copyButton.setVisible(true);
            }
        );

        copyButton.addActionListener(
            event ->
            {
                if(FilesCopy.haveSelectedSrcAndDes())
                {
                    FilesCopy.copy();
                    copyButton.setVisible(false);
                    remove(copyButton);
                    add(traverseMethodButton);
                    traverseMethodButton.setVisible(true);
                }
                else
                    JOptionPane.showMessageDialog(null,"請先選擇原始檔(夾)與目標資料夾!");
            }
        );

        setLayout(new GridLayout(3,3));
        setTitle("複製檔案");
        setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,400);
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        add(src);
        add(des);
        add(traverseMethodButton);
    }

    public static void main(String[] args) {
        new Test();
    }
}

class FilesCopy
{
    private static File src = null;
    private static File des = null;
    private static long desSize = 0;
    private static long srcSize = 0;
    private static boolean [] traverseMethod = {false,false,false};
    private static boolean[] copyMethod = { false,false};
    private static JFrame showProgressBar = new JFrame();
    private static JProgressBar copyProgressBar = new JProgressBar();
    private static JTextField textField = new JTextField();
    private static int index = 0;

    private static int getCopyPercent()
    {
        return (int)(desSize * 100.0 / srcSize);
    }

    private static void addCopySize() {
        desSize += 1024L;
    }

    public static void setTraverseByListFiles()
    {
        traverseMethod[0] = true;
    }

    private static void traverseByListFiles(File srcFile,File desFile) throws IOException
    {
        if(srcFile.isDirectory())
        {
            File[] files = srcFile.listFiles();
            assert files != null;
            for(File file : files)
            {
                File desFileOrDir = new File(desFile.getAbsolutePath() + File.separator + file.getName());
                if(file.isDirectory())
                {
                    if(desFileOrDir.exists())
                        desFileOrDir.delete();
                    desFileOrDir.mkdirs();
                }
                traverseByListFiles(file,desFileOrDir);
            }
        }
        else {
            copyFile(srcFile,desFile);
        }
    }

    public static void setTraverseByList()
    {
        traverseMethod[1] = true;
    }

    private static void traverseByList(File srcFile,File desFile) throws IOException
    {
        if (srcFile.isDirectory())
        {
            String[] files = srcFile.list();
            assert files != null;
            for (String file : files)
            {
                File subSrcFile = new File(srcFile,file);
                File subDesFile = new File(desFile,file);
                if (subSrcFile.isDirectory())
                {
                    if (subDesFile.exists())
                        subDesFile.delete();
                    subDesFile.mkdirs();
                }
                traverseByList(subSrcFile,subDesFile);
            }
        }
        else
        {
            copyFile(srcFile,desFile);
        }

    }

    public static void setTraverseByGetfiles()
    {
        traverseMethod[2] = true;
    }

    private static void traverseByGetFiles(File srcFile,File desFile) throws IOException
    {
        if (srcFile.isDirectory())
        {
            java.util.List<File> fileList = org.codehaus.plexus.util.FileUtils.getFiles(srcFile,null);
            for (File file : fileList)
            {
                File desFileOrDir = new File(desFile.getAbsolutePath() + File.separator + file.getName());
                if(file.isDirectory())
                {
                    if(desFileOrDir.exists())
                        desFileOrDir.delete();
                    desFileOrDir.mkdirs();
                }
                traverseByListFiles(file,desFileOrDir);
            }
        }
        else
        {
            copyFile(srcFile,desFile);
        }
    }

    public static void setTraverseByCommonsIO()
    {
        traverseMethod[3] = true;
    }

    private static void traverseByCommonsIO(File srcFile,File desFile) throws IOException
    {
        if (srcFile.isDirectory())
        {
            Collection<File> files = org.apache.commons.io.FileUtils.listFiles(srcFile,false);
            for (File file : files)
            {
                File desFileOrDir = new File(desFile.getAbsolutePath() + File.separator + file.getName());
                if(file.isDirectory())
                {
                    if(desFileOrDir.exists())
                        desFileOrDir.delete();
                    desFileOrDir.mkdirs();
                }
                traverseByCommonsIO(file,desFile);
        }
    }

    public static void setTraverseByNIO2()
    {
        traverseMethod[4] = true;
    }

    private static void traverseByNIO2(File srcFile) throws IOException
    {
        java.nio.file.Files.walkFileTree(srcFile.toPath(),new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(Path path,BasicFileAttributes attrs) throws IOException {
                File d = new File(des.toString() + path.toAbsolutePath().toString().substring(src.toString().length()));
                new File(d.toString().substring(0,d.toString().lastIndexOf(File.separator))).mkdirs();
                copyFile(path.toFile(),d);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    public static void setCopyByFileStream()
    {
        copyMethod[0] = true;
    }

    private static void copyByFileStream(File srcFile,File desFile) throws IOException
    {
        FileInputStream inputStream = new FileInputStream(srcFile);
        FileOutputStream outputStream = new FileOutputStream(desFile);
        byte [] b = new byte[1024];
        while(inputStream.read(b) != -1)
        {
            outputStream.write(b);
            addCopySize();
        }
        inputStream.close();
        outputStream.close();
    }

    public static void setCopyByBufferStream()
    {
        copyMethod[1] = true;
    }

    private static void copyByBufferStream(File srcFile,File desFile) throws IOException
    {
        BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(srcFile));
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(desFile));
        byte [] b = new byte[1024];
        while(inputStream.read(b) != -1)
        {
            addCopySize();
            outputStream.write(b);
        }
        inputStream.close();
        outputStream.close();
    }

    public static void setCopyByFileReader()
    {
        copyMethod[2] = true;
    }

    private static void copyByFileReader(File srcFile,File desFile) throws IOException
    {
        FileReader reader = new FileReader(srcFile);
        FileWriter writer = new FileWriter(desFile);

        char [] c = new char[1024];
        while(reader.read(c) != -1)
        {
            addCopySize();
            writer.write(c);
        }
        reader.close();
        writer.close();
    }

    public static void setCopyByBufferReader()
    {
        copyMethod[3] = true;
    }

    private static void copyByBufferReader(File srcFile,File desFile) throws IOException
    {
        BufferedReader reader = new BufferedReader(new FileReader(srcFile));
        BufferedWriter writer = new BufferedWriter(new FileWriter(desFile));

        char [] c = new char[1024];
        while(reader.read(c) != -1)
        {
            addCopySize();
            writer.write(c);
        }
        reader.close();
        writer.close();
    }

    public static void setCopyByFileChannel()
    {
        copyMethod[4] = true;
    }

    private static void copyByFileChannel(File srcFile,File desFile) throws IOException
    {
        FileChannel srcChannel = new FileInputStream(srcFile).getChannel();
        FileChannel desChannel = new FileOutputStream(desFile).getChannel();
        srcChannel.transferTo(0,desChannel);
        srcChannel.close();
        desChannel.close();
    }

    public static void setCopyByFileChannelWithBuffer()
    {
        copyMethod[5] = true;
    }

    private static void copyByFileChannelWithBuffer(File srcFile,File desFile) throws IOException
    {
        FileChannel srcChannel = new FileInputStream(srcFile).getChannel();
        FileChannel desChannel = new FileOutputStream(desFile).getChannel();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        while(srcChannel.read(buffer) != -1)
        {
            buffer.flip();
            desChannel.write(buffer);
            buffer.clear();
            addCopySize();
        }
        srcChannel.close();
        desChannel.close();
    }

    public static void setCopyByCommonsIO()
    {
        copyMethod[6] = true;
    }

    private static void copyByCommonsIO(File srcFile,File desFile) throws IOException
    {
        FileUtils.copyFile(srcFile,desFile);
    }

    public static void setCopyByFiles()
    {
        copyMethod[7] = true;
    }

    private static void copyByFiles(File srcFile,File desFile) throws IOException
    {
        Files.copy(srcFile.toPath(),StandardCopyOption.REPLACE_EXISTING);
    }

    public static void setSrc(File srcFile) {
		src = srcFile;
		if(srcFile.isDirectory())
		    srcSize = org.apache.commons.io.FileUtils.sizeOfDirectory(srcFile);
		else
            srcSize = src.length();
	}

    public static void setDes(File desFile) {
        des = desFile;
        desSize = 0;
    }

    public static void setSrc(Path srcPath)
    {
        setSrc(srcPath.toFile());
    }

    public static void setDes(Path desPath)
    {
        setDes(desPath.toFile());
    }

    private static void copyFile(File srcFile,File desFile) throws IOException
    {
        if (copyMethod[0])
            copyByFileStream(srcFile,desFile);
        else if (copyMethod[1])
            copyByBufferStream(srcFile,desFile);
        else if (copyMethod[2])
            copyByFileReader(srcFile,desFile);
        else if (copyMethod[3])
            copyByBufferReader(srcFile,desFile);
        else if (copyMethod[4])
            copyByFileChannel(srcFile,desFile);
        else if (copyMethod[5])
            copyByFileChannelWithBuffer(srcFile,desFile);
        else if (copyMethod[6])
            copyByCommonsIO(srcFile,desFile);
        else if (copyMethod[7])
            copyByFiles(srcFile,desFile);
        else
            showMessage("複製失敗,找不到複製方法.");
    }

    private static void showMessage(String message)
    {
        JOptionPane.showMessageDialog(null,message);
    }

    public static boolean haveSelectedSrcAndDes()
    {
        return src != null && des != null;
    }

    public static void copy()
    {
        long start = System.currentTimeMillis();
        if(haveSelectedSrcAndDes())
        {
            if(src.isFile())
            {
                des = new File(des.getAbsolutePath()+File.separator+src.getName());
            }
            SwingWorker<String,Object> copyTask = new SwingWorker<>()
            {
                @Override
                protected String doInBackground()
                {
                    try
                    {
                        if (traverseMethod[0])
                            traverseByListFiles(src,des);
                        else if (traverseMethod[1])
                            traverseByList(src,des);
                        else if (traverseMethod[2])
                            traverseByGetFiles(src,des);
                        else if (traverseMethod[3])
                            traverseByCommonsIO(src,des);
                        else if (traverseMethod[4])
                            traverseByNIO2(src);
                        else
                        {
                            showProgressBar.dispose();
                            showMessage("遍歷失敗,找不到遍歷方法");
                        }
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                        showProgressBar.dispose();
                        showMessage("未知錯誤複製失敗");
                    }
                    finish(start);
                    return null;
                }
            };
            copyTask.execute();
            if (!copyMethod[4] && !copyMethod[6] && !copyMethod[7])
            {
                copyProgressBar.setMinimum(0);
                copyProgressBar.setMaximum(100);
                copyProgressBar.setValue(0);
                copyProgressBar.setVisible(true);
                copyProgressBar.setStringPainted(true);

                showProgressBar.add(copyProgressBar);
                showProgressBar.setTitle("複製進度");
                showProgressBar.setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 150,(int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - 50,300,100);
                showProgressBar.setVisible(true);
                new Thread(
                        () ->
                        {
                            int percent;
                            while ((percent = getCopyPercent()) < 100)
                            {
                                try
                                {
                                    Thread.sleep(100);
                                }
                                catch(InterruptedException e)
                                {
                                    e.printStackTrace();
                                }
                                copyProgressBar.setValue(percent);
                            }
                        }
                ).start();
            }
            else
            {

                final String [] text = {".","..","...","....",".....",".......","......","."};
                textField.setVisible(true);
                textField.setHorizontalAlignment(JTextField.CENTER);
                textField.setEditable(false);
                showProgressBar.add(textField);
                showProgressBar.setTitle("複製中");
                showProgressBar.setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 120,(int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - 40,240,80);
                showProgressBar.setVisible(true);

                new Thread(
                        () ->
                        {
                            while (getCopyPercent() < 100)
                            {
                                try
                                {
                                    Thread.sleep(400);
                                }
                                catch(InterruptedException e)
                                {
                                    e.printStackTrace();
                                }
                                if(index < text.length)
                                    textField.setText("複製中"+text[index++]);
                                else
                                    index = 0;
                            }
                        }
                ).start();
            }
        }
    }

    private static void finish(long start)
    {
        long end = System.currentTimeMillis();
        showProgressBar.dispose();
        showMessage("複製成功,用時:" + (end - start) / 1000.0 + "s");

        copyProgressBar.setVisible(false);
        showProgressBar.remove(copyProgressBar);
        textField.setVisible(false);
        showProgressBar.remove(textField);

        Arrays.fill(traverseMethod,false);
        Arrays.fill(copyMethod,false);
        des = src = null;
        desSize = srcSize;
    }
}
複製程式碼