目前爲止,我們已經學習了很多 Java 拷貝文件的方式,除了 FileChannel 提供的方法外,還包括使用 Files.copy() 或使用字節數組的緩衝/非緩衝流。那個纔是最好的選擇呢?這個問題很難回答,因爲答案基於很多因素。本文將目光集中到一個因素,那就是速度,因爲拷貝任務 越快將會提高效率,在有些情況下,這是成功的關鍵。因此,本文將使用一個應用程序來比較下面這些拷貝方式的具體時間:
- FileChannel 和非直接模式的 ByteBuffer
- FileChannel 和直接模式的 ByteBuffer
- FileChannel.transferTo()
- FileChannel.transferFrom()
- FileChannel.map()
- 使用字節數組和緩衝流
- 使用字節數組和非緩衝流
- File.copy()(Path 到 Path,InputStream 到 Path 和 Path 到 OutputStream)
應用程序基於下面的條件:
- 拷貝文件類型 MP4 視頻(文件名爲 Rafa Best Shots.mp4,所在目錄爲 C:\rafaelnadal\tournaments\2009\videos)
- 文件大小:58.3MB
- 測試的緩衝區大小:4KB, 16KB, 32KB, 64KB, 128KB, 256KB, and 1024KB
- 機器配置:Mobile AMD Sempron Processor 3400 + 1.80 GHz, 1.00GB RAM, 32-bit
OS, Windows 7 Ultimate - 測量類型:使用 System.nanoTime() 方法
- 連續運行三次後再獲取時間;前三次運行將會被忽略。開始運行的時間總會比後面運行的時間要長一些。
下面將列出完整的應用程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
import java.nio.MappedByteBuffer; import java.io.OutputStream; import java.io.InputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.EnumSet; import static java.nio.file.LinkOption.NOFOLLOW_LINKS; public class Main
{ public static void deleteCopied(Path
path){ try { Files.deleteIfExists(path); } catch (IOException
ex) { System.err.println(ex); } } public static void main(String[]
args) { final Path
copy_from = Paths.get("C:/rafaelnadal/tournaments/ 2009 /videos/ Rafa
Best Shots.mp4"); final Path
copy_to = Paths.get( "C:/Rafa
Best Shots.mp4" ); long startTime,
elapsedTime; int bufferSizeKB
= 4 ; //also
tested for 16, 32, 64, 128, 256 and 1024 int bufferSize
= bufferSizeKB * 1024 ; deleteCopied(copy_to); //FileChannel
and non-direct buffer System.out.println( "Using
FileChannel and non-direct buffer ..." ); try (FileChannel
fileChannel_from = (FileChannel.open(copy_from, EnumSet.of(StandardOpenOption.READ))); FileChannel
fileChannel_to = (FileChannel.open(copy_to, EnumSet.of(StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE)))) { startTime
= System.nanoTime(); //
Allocate a non-direct ByteBuffer ByteBuffer
bytebuffer = ByteBuffer.allocate(bufferSize); //
Read data from file into ByteBuffer int bytesCount; while ((bytesCount
= fileChannel_from.read(bytebuffer)) > 0 )
{ //flip
the buffer which set the limit to current position, and position to 0 bytebuffer.flip(); //write
data from ByteBuffer to file fileChannel_to.write(bytebuffer); //for
the next read bytebuffer.clear(); } elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); //FileChannel
and direct buffer System.out.println( "Using
FileChannel and direct buffer ..." ); try (FileChannel
fileChannel_from = (FileChannel.open(copy_from, EnumSet.of(StandardOpenOption.READ))); FileChannel
fileChannel_to = (FileChannel.open(copy_to, EnumSet.of(StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE)))) { startTime
= System.nanoTime(); //
Allocate a direct ByteBuffer ByteBuffer
bytebuffer = ByteBuffer.allocateDirect(bufferSize); //
Read data from file into ByteBuffer int bytesCount; while ((bytesCount
= fileChannel_from.read(bytebuffer)) > 0 )
{ //flip
the buffer which set the limit to current position, and position to 0 bytebuffer.flip(); //write
data from ByteBuffer to file fileChannel_to.write(bytebuffer); //for
the next read bytebuffer.clear(); } elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); //FileChannel.transferTo() System.out.println( "Using
FileChannel.transferTo method ..." ); try (FileChannel
fileChannel_from = (FileChannel.open(copy_from, EnumSet.of(StandardOpenOption.READ))); FileChannel
fileChannel_to = (FileChannel.open(copy_to, EnumSet.of(StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE)))) { startTime
= System.nanoTime(); fileChannel_from.transferTo(0L,
fileChannel_from.size(), fileChannel_to); elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); //FileChannel.transferFrom() System.out.println( "Using
FileChannel.transferFrom method ..." ); try (FileChannel
fileChannel_from = (FileChannel.open(copy_from, EnumSet.of(StandardOpenOption.READ))); FileChannel
fileChannel_to = (FileChannel.open(copy_to, EnumSet.of(StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE)))) { startTime
= System.nanoTime(); fileChannel_to.transferFrom(fileChannel_from,
0L, ( int )
fileChannel_from.size()); elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); //FileChannel.map System.out.println( "Using
FileChannel.map method ..." ); try (FileChannel
fileChannel_from = (FileChannel.open(copy_from, EnumSet.of(StandardOpenOption.READ))); FileChannel
fileChannel_to = (FileChannel.open(copy_to, EnumSet.of(StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE)))) { startTime
= System.nanoTime(); MappedByteBuffer
buffer = fileChannel_from.map(FileChannel.MapMode.READ_ONLY, 0 ,
fileChannel_from.size()); fileChannel_to.write(buffer); buffer.clear(); elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); //Buffered
Stream I/O System.out.println( "Using
buffered streams and byte array ..." ); File
inFileStr = copy_from.toFile(); File
outFileStr = copy_to.toFile(); try (BufferedInputStream
in = new BufferedInputStream( new FileInputStream(inFileStr)); BufferedOutputStream
out = new BufferedOutputStream( new FileOutputStream(outFileStr)))
{ startTime
= System.nanoTime(); byte []
byteArray = new byte [bufferSize]; int bytesCount; while ((bytesCount
= in.read(byteArray)) != - 1 )
{ out.write(byteArray, 0 ,
bytesCount); } elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); System.out.println( "Using
un-buffered streams and byte array ..." ); try (FileInputStream
in = new FileInputStream(inFileStr); FileOutputStream
out = new FileOutputStream(outFileStr))
{ startTime
= System.nanoTime(); byte []
byteArray = new byte [bufferSize]; int bytesCount; while ((bytesCount
= in.read(byteArray)) != - 1 )
{ out.write(byteArray, 0 ,
bytesCount); } elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
ex) { System.err.println(ex); } deleteCopied(copy_to); System.out.println( "Using
Files.copy (Path to Path) method ..." ); try { startTime
= System.nanoTime(); Files.copy(copy_from,
copy_to, NOFOLLOW_LINKS); elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
e) { System.err.println(e); } deleteCopied(copy_to); System.out.println( "Using
Files.copy (InputStream to Path) ..." ); try (InputStream
is = new FileInputStream(copy_from.toFile()))
{ startTime
= System.nanoTime(); Files.copy(is,
copy_to); elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
e) { System.err.println(e); } deleteCopied(copy_to); System.out.println( "Using
Files.copy (Path to OutputStream) ..." ); try (OutputStream
os = new FileOutputStream(copy_to.toFile()))
{ startTime
= System.nanoTime(); Files.copy(copy_from,
os); elapsedTime
= System.nanoTime() - startTime; System.out.println( "Elapsed
Time is " +
(elapsedTime / 1000000000.0 )
+ "
seconds" ); } catch (IOException
e) { System.err.println(e); } } } |
輸出結果排序比較複雜,其中包含了很多數據。下面我將主要的對比用圖形的方式展示出來。圖形中 Y 座標表示消耗的時間(單位:秒),X 座標表示緩衝的大小(或運行次數,跳過了前三次運行)。
FileChannel 和非直接模式 Buffer vs. FileChannel 和直接模式 Buffer
從下圖看來,如果緩存小於 256KB,那麼非直接模式的 Buffer 快一點,而緩存大於 256KB 後,直接模式的 Buffer 快一點:
FileChannel.transferTo() vs. FileChannel.transferFrom() vs. FileChannel.map()
從下圖看來,FileChannel.transferTo() 和 FileChannel.transferFrom 運行七次的速度都差不多,而 FileChannel.map 的速度就要差很多:
三種 Files.copy() 方法
從下圖看來,最快的是 Path 到 Path,其次是 Path 到 OutputStream,最慢的是 InputStream 到 Path:
FileChannel 和非直接模式 Buffer vs. FileChannel.transferTo() vs. Path 到 Path
最後,我們將前面最快的三種方式綜合起來比較。從比較的結果來看,似乎 Path 到 Path 是最快的解決方案: