Методы многократного копирования файлов Java и сравнение эффективности

Java

1. Предпосылки

На самом деле существует множество способов копирования файлов в java, которые можно разделить на

  • Традиционный поток байтов для чтения и записи копирует FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream
  • Традиционное копирование потока символов для чтения и записи FileReader, FileWriter, BufferWriter, BufferedWriter, BufferedReader
  • FileChannel для серии NIO
  • 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.file.Files.walkFileTree в java nio

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()

К тому же автор не хочет смотреть на консоль...поэтому пользуюсь с небольшим замахом.

3.баночный пакет

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);
	}
}

Получите все подфайлы и подпапки с помощью listFiles() из srcFile, а затем оцените, является ли это каталогом. Если это каталог, сначала определите, есть ли этот файл (иногда это папка, но есть файл с таким же именем, сначала удалите его), затем создайте папку, а затем рекурсивно выполните функцию. Если это не каталог, напрямую используйте два файла в качестве параметров для копирования файла, а какой метод используется в нем, будет установлено позже.

(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,null);

java.util. Список возвращаемых результатов

(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,null,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);
	}
}

Используйте метод listFiles из org.apache.commons.io.FileUtils, параметр — это каталог, который нужно пройти, null и false, второй параметр представляет собой фильтр, что означает отфильтровывать файлы с определенным суффиксом, тип is 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 может принимать значения:

  • ПРОДОЛЖИТЬ: продолжить
  • ЗАВЕРШЕНИЕ: конец
  • 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();
}

Давайте поговорим о разнице между тремя методами чтения.FileInputStream имеет три метода чтения:

input.read();
input.read(b);
input.read(b,off,len);

A.read()

Читать байт за байтом, возвращать int, использовать write(n) непосредственно при записи;

int n = input.read();
output.write(n);

Можно сказать, что это самое медленное из трех чтений.... Автор попробовал файл размером около 2G, и копирование 160M заняло около 10 минут...

B.read(b)

Параметр представляет собой byte[], буферизует в него байты и возвращает количество байтов в массиве, что намного быстрее, чем read().

byte [] b = new byte[1024];
while(input.read(b) != -1)
	output.write(b);

C.read(b,off,len)

Этот метод на самом деле похож на read(b), который эквивалентен read(b, off, len) с опущенными параметрами.

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

public int read(byte b[]) throws IOException 
{
	return readBytes(b, 0, b.length);
}

Оба они вызывают один и тот же readBytes():

private native int readBytes(byte b[], int off, 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 (на самом деле есть еще и readN, и read(), read() точно самая медленная, и автор readN редко ее использует, поэтому ее нет в списке)

read(b);
read(b,off,len);
readAllBytes();

A.read(b)

На самом деле это ничем не отличается от FileInputStream, просто поместите в него массив байтов.

B.read(b,off,len)

Это.... также ничем не отличается от FileInputStream, не говоря уже о

C.readAllBytes()

Это может считывать все байты за раз.Однако, хотя это просто в использовании, вы можете напрямую

output.write(input.readAllBytes());

Однако есть цена:

在这里插入图片描述
Будет ошибка OutOfMemory, то есть для больших файлов их лучше честно разделить, не "достать за один присест" или "съесть еще несколько".

Оцените эффективность:

在这里插入图片描述
readAllBytes напрямую взрывает память для больших файлов (у автора файл в пределах 5G)....
在这里插入图片描述
在这里插入图片描述
readAllBytes() снова взорвался... Этот файл меньше 2G... readAllBytes() не кажется очень мощным.... Но эффективность для небольших файлов приемлема.

(3)FileReader+FileWriter

Чтение и запись потока символов FileReader+FileWriter, по сравнению с чтением потока байтов, в основном заменяет 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. По сравнению с FileReader, BufferedReader имеет метод readLine(), который может читать каждую строку, что быстрее, чем FileReader. Соответствующий BufferedWriter предоставляет метод записи (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();
}

флип означает «флип»,

buffer.flip();

Измените буфер из режима записи в режим чтения, затем запишите (буфер), а затем очистите буфер. Взгляните на эффективность этих двух методов:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Кроме того, автор обнаружил, что «верхний предел» transferTo составляет 2 ГБ, что означает, что один файл размером более 2 ГБ может быть скопирован максимум до 2 ГБ. Итак... нет сравнения для больших файлов.

(7)FileUtils.copyFile()

Это инструментальный класс, тут и говорить нечего, параметры два файла, которые представляют исходник и цель соответственно.

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

(8)Files.copy()

Это официально предоставленный класс инструментов "Файлы". Первые два параметра – "Путь", которые представляют исходный и целевой объекты соответственно. Третий параметр (или опущенный) можно задать для представления параметров. Например, вы можете установить

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) качели

А. Макет сетки

Основной JFrame использует макет сетки

setLayout(new GridLayout(3,1,5,3));

Три строки и один столбец, потому что кнопок всего три, выберите исходный файл (папку), выберите целевую папку и выберите метод обхода. JFrames с параметрами обхода/копирования также применяют макет сетки:

showTraverseMethod.setLayout(new GridLayout(5,1,3,3));
showCopyMethod.setLayout(new GridLayout(4,2,5,5));

Б. по центру

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, метод TransferTo FileChannel может записывать только файлы размером до 2G, поэтому для файлов размером более 2G копируется только 2G, поэтому эта строка FileChannel не очень сравнима.Для текстовых файлов скорость копирования BufferedReader/Writer Это самый быстрый, за ним следует FileInput/OutputStream.Для одного большого файла FileUtils apache и Files.copy NIO медленнее, чем 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 сильно различаются по производительности, в то время как listFiles от apache или walkFileTree от java nio относительно стабильны и имеют хорошую скорость Рекомендуется использовать эти два метода для обхода каталогов.

(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, разница не слишком большая, эффект близок, но разрыв немного велик, когда это 1G. С точки зрения обхода, WalkFileTrees java nio является самым быстрым.

Конечно, эти тесты предназначены только для справки. Какой из них использовать, зависит от конкретной среды. Кроме того, этот метод отделяет обход от копирования. У 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,1,3,3));
                showTraverseMethod.setTitle("遍历方式");
                showTraverseMethod.setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,
                        (int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - 200, 400, 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,2,5,5));
                showCopyMethod.setTitle("复制方式");
                showCopyMethod.setBounds(
                        (int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,
                        (int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - 200, 400, 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,1,5,3));
        setTitle("复制文件");
        setBounds((int) (Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - 200,
                (int) (Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - 200, 400, 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,false,false,false};
    private static boolean[] copyMethod = { false, false, false, false, false, false ,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,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,null,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);
        }
    }

    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,srcChannel.size(),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(), desFile.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;
    }
}