都知道NIO在讀取大文件的時候都比較快座云。但是在小文件的寫入就不是這樣了(這個例子源于使用1G的內存如何找到10G大小的文件出現頻率最高的數字,后來覺得NIO讀寫大文件有優(yōu)勢旁理,那么小文件讀寫也應該比較快吧7恪)。
下面是一個使用NIO來像文件寫入String的例子:
package nio;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOWriter {
private FileChannel fileChannel;
private ByteBuffer buf;
@SuppressWarnings("resource")
public NIOWriter(File file, int capacity) {
try {
TimeMonitor.start();
fileChannel = new FileOutputStream(file).getChannel();
TimeMonitor.end("fileChannel = new FileOutputStream(file).getChannel()");
buf = ByteBuffer.allocate(capacity);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 不采用遞歸是因為如果字符串過大而緩存區(qū)過小引發(fā)StackOverflowException
*
* @param str
* @throws IOException
*/
public void write(String str) throws IOException {
TimeMonitor.start();
int length = str.length();
byte[] bytes = str.getBytes();
int startPosition = 0;
do {
startPosition = write0(bytes, startPosition);
} while (startPosition < length);
TimeMonitor.end("write(String str)");
}
public int write0(byte[] bytes, int position) throws IOException {
if (position >= bytes.length) {
return position;
}
while (buf.hasRemaining()) {
if (position < bytes.length) {
buf.put(bytes[position]);
position++;
} else {
break;
}
}
buf.flip();
fileChannel.write(buf);
buf.clear();
return position;
}
/**
* 強制寫入數據。并且關閉連接
*/
public void close() {
try {
fileChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后使用JMH與其他小文件寫入方式來作比較驻襟。按理說夺艰,使用NIO的方式每次都有緩存,速度應該會很快沉衣,但實際卻不是這樣郁副。下面做了一個比較,分別使用FileWriter豌习,buffer存谎,直接寫,以及NIO的方式:
package nio;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
public class WriterTest {
static int writeCount = 1;
static String inputStr = "very woman is a" + " treasure but way too often we forget"
+ " how precious they are. We get lost in daily "
+ "chores and stinky diapers, in work deadlines and dirty dishes, in daily errands and occasional breakdowns.";
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder().include(WriterTest.class.getSimpleName()).forks(1).build();
new Runner(opt).run();
}
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void TestFileWriter() throws IOException {
File file = new File("/Users/xujianxing/Desktop/fileWithWriter.txt");
FileWriter fileWriter = new FileWriter(file);
for (int i = 0; i < writeCount; i++) {
fileWriter.write("JAVA TEST");
}
}
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void TestBuffer() throws IOException {
File file = new File("/Users/xujianxing/Desktop/buffer.txt");
BufferedOutputStream buffer = new BufferedOutputStream(new FileOutputStream(file));
for (int i = 0; i < writeCount; i++) {
buffer.write(inputStr.getBytes());
}
buffer.flush();
buffer.close();
}
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void TestNormal() throws IOException {
File file = new File("/Users/xujianxing/Desktop/normal.txt");
FileOutputStream out = new FileOutputStream(file);
for (int i = 0; i < writeCount; i++) {
out.write(inputStr.getBytes());
}
out.close();
}
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void TestNIO() throws IOException {
File file = new File("/Users/xujianxing/Desktop/nio.txt");
NIOWriter nioWriter = new NIOWriter(file, 2048);
for (int i = 0; i < writeCount; i++) {
nioWriter.write(inputStr);
}
nioWriter.close();
}
}
然而測試的結果卻大跌眼鏡肥隆,看一看測試的結果:
Benchmark Mode Cnt Score Error Units
WriterTest.TestBuffer ss 0.265 ms/op
WriterTest.TestFileWriter ss 0.232 ms/op
WriterTest.TestNIO ss 8.479 ms/op
WriterTest.TestNormal ss 0.217 ms/op
NIO的測試結果是最差的既荚,結果卻花了8ms之多!而且寫次數越多栋艳,差距越大恰聘。
這到底是為什么呢?
寫了一個簡單的測試類來記錄花費的時間:
package nio;
public class TimeMonitor {
private static long start = 0;
private static long end = 0;
public static void start() {
start = System.currentTimeMillis();
end = 0;
}
public static void end(String tag) {
end = System.currentTimeMillis();
System.out.println("time coast:"+tag+"---------->" + (end - start));
end = 0;
start = 0;
}
}
然后在覺得可能會耗時的地方上(比如channel的獲得吸占,channel的寫入等)輸出結果是這樣的:
time coast:fileChannel = new FileOutputStream(file).getChannel()---------->5
time coast:write0---------->0
time coast:fileChannel.write(buf)---------->2
從輸出的結果看晴叨,果然是在創(chuàng)建channel的時候已經寫channel的時候花費了大量的時間。但是多余的一毫秒多哪去了旬昭?
改正一下測試類篙螟,結果是這樣的:
@Benchmark
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void TestNIO() throws IOException {
TimeMonitor.start();
File file = new File("/Users/xujianxing/Desktop/nio.txt");
NIOWriter nioWriter = new NIOWriter(file, 2048);
// TimeMonitor.end("NIOWriter nioWriter = new NIOWriter(file, 2048);");
for (int i = 0; i < writeCount; i++) {
// TimeMonitor.start();
nioWriter.write(inputStr);
// TimeMonitor.end("nioWriter.write(inputStr)");
}
// TimeMonitor.start();
nioWriter.close();
TimeMonitor.end(" nioWriter.close()");
}
}
輸出結果是這樣的(不同的機器可能會不一樣,每次耗費的時間也不一樣):
time coast: nioWriter.close()---------->6
發(fā)現问拘,耗費多出的1毫秒多應該是JMH自己本身耗費的時間遍略。但奇怪的是測試其他普通方式讀寫卻沒有發(fā)現這種情況,為什么會這樣尚不得而知骤坐。不過绪杏,不要使用NIO讀取小文件肯定是正確的。