最近把IO和NIO相關文檔複習了下,抽空作下總結。
1,常用API的用法,這裏只考慮寫操作,讀操作下次專門寫。
2,API之間的效率,及系統相關影響。
3,模擬最常用的寫字符文件。
4,對比兩類大小的文件:100M和1G。
5,儘量排除字符轉字節帶來的性能影響。
6,本人電腦配置I7-4790U,硬盤是SSD。
主要對比API效率如下:
1,FileOutputStream:沒緩存速度慢,基於字節處理。
2,BufferedOutputStream:速度快
3,FileWriter:沒緩存速度最慢,基於字符處理。
4,BufferedWriter:速度快。和BufferedOutputStream差不多,主要慢在底層字符轉字節需要處理。
5,PrintWriter:速度快,封裝了BufferedWriter,有處理文件行方法。
6,RandomAccessFile:速度慢,可以操作文件指針。
7,ByteBuffer:速度很快,但是需要注意使用方法。加大每次寫入塊的大小,可以明顯提升速度。但是如果一行一行的寫入文件,效率還是很差。
8,MappedByteBuffer:速度最快,秒殺上面各位。功能上是RandomAccessFile的爸爸。但是缺陷也有,消耗內存很大。另外處理最大文件大小爲2G,主要是size參數爲int。
其實上面有幾個是差不多的類,無非只是用了裝飾器模式。
可能是我電腦的原因,寫入100M文件時大家效率差距還挺大的,但是寫入1G時,差距會縮小。
內存消耗除了MappedByteBuffer,其實都差不多。如果帶有flush()操作,消耗會少點。
下面直接貼測試代碼同時也是API的用法。
1,FileOutputStream:
寫一個大約100m文件
時間消耗:1726ms
寫一個大約1G文件
時間消耗:18074ms,內存消耗約:350M+
public static void writeFileStream() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
FileOutputStream fos =new FileOutputStream("file.txt");
String str = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n";
byte[] bytes = str.getBytes();
for(int i = 0 ; i<10000000;i++) {
fos.write(bytes);
fos.flush();
}
fos.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
2,BufferedOutputStream
寫一個大約100m文件
時間消耗:157ms
寫一個大約1G文件
時間消耗:8740ms,內存消耗300M+
public static void bufferFileStream() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
FileOutputStream fos =new FileOutputStream("file1.txt");
//默認緩存區大小爲8192,擴大緩存區提升效果不明顯(緩存區擴大5倍時100M的寫入速度大約爲120ms)
BufferedOutputStream bos = new BufferedOutputStream(fos);
String str = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n";
byte[] bytes = str.getBytes();
for(int i = 0 ; i<10000000;i++) {
bos.write(bytes);
}
bos.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
3,FileWriter
寫一個大約100m文件
時間消耗:2000ms。
寫一個大約1G文件
時間消耗:23484ms。內存消耗:150M+
public static void writerFile() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
FileWriter w = new FileWriter("file2.txt");
String str = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n";
for(int i = 0 ; i<1000000;i++) {
w.write(str);
w.flush();//這裏一般情況是要flush,雖然不加速度快很多。但是容易丟失數據,也容易內存溢出。
}
w.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
4,BufferedWriter
寫一個大約1G文件
時間消耗:8509ms。
寫一個大約100m文件
時間消耗:270ms。內存消耗:450M+
public static void bufferWriterFile() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
//緩存區擴大後,和BufferedOutputStream類一樣,效果不明顯。所有還得根據實際情況進行調整。
//BufferedWriter比BufferedOutputStream慢的原因,主要時字符串還需要轉字節處理。其實兩者差距很小。
//只是應用的場景不同而已
BufferedWriter bw = new BufferedWriter(new FileWriter("file3.txt"));
String str = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n";
for(int i = 0 ; i<10000000;i++) {
bw.write(str);
}
bw.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
5,PrintWriter
底層其實也是bufferWriter但是封裝了一些處理文件行的方法
寫一個大約1G文件
時間消耗:8553ms
寫一個大約100m文件
時間消耗:320ms
public static void printWriter() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
PrintWriter pw = new PrintWriter("file4.txt");
String str = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n";
for(int i = 0 ; i<1000000;i++) {
pw.println(str);
}
pw.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
6,RandomAccessFile
寫一個大約1G文件
時間消耗:8553ms.
寫一個大約100m
時間消耗:1593ms
public static void randomAccessFile() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
RandomAccessFile file = new RandomAccessFile("file5.txt","rw");
String str = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上";
for(int i = 0 ; i<1000000;i++) {
file.writeUTF(str);
}
file.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
7.1,ByteBuffer
利用NIO的ByteBuffer來直接寫,這個地方其實可以優化的,因爲這麼直接寫並不是FileChannel的強項。
寫一個大約100M文件
時間消耗:1799ms.
寫一個大約1G文件
時間消耗:18687ms.
public static void byteBuffer1() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
FileChannel fc = new FileOutputStream("file6.txt").getChannel();
ByteBuffer bb =ByteBuffer.allocate(1024);
byte[] b = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n".getBytes("UTF-8");
for(int i = 0 ; i<1000000;i++) {
bb = bb.wrap(b);//不會改變ByteBuffer遊標位置.如果使用put()方法會改變遊標的位置,再次寫的時候需要使用bb.flip();
while(bb.hasRemaining()) {//確保ByteBuffer中的數據完全寫進去了。
fc.write(bb);
}
bb.clear();
}
fc.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
7.2,ByteBuffer
寫一個大約100M文件
時間消耗:94ms.
寫一個大約1G文件
時間消耗:7286ms.
看得出效率很明顯的提升,而且在小文件上更佔優,估計是文件太大瓶頸在系統層面的IO上。NIO是基於塊的操作,但是如果塊太小也就沒什麼優勢了,所以這裏我們人爲加大塊的範圍,提升效率。
public static void byteBuffer2() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
FileChannel fc = new FileOutputStream("file6.txt").getChannel();
ByteBuffer bb =ByteBuffer.allocate(102400*5);
byte[] b = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n".getBytes("UTF-8");
for(int i = 0 ; i<10000000;i++) {
bb.put(b);
if(i%500==0||i==9999999) {
bb.flip();
while(bb.hasRemaining()) {
fc.write(bb);
}
bb.clear();
}
}
fc.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
8,MappedByteBuffer
寫一個大約100M文件
大約消耗78ms
寫一個大約1G的文件
大約用時735ms,內存消耗:1G+
這裏提升太明顯了,簡直是秒殺上面各位,當然缺點也很明顯內存消耗大。但是文件映射單獨作寫的操作並不是很好,主要是文件的大小一開始就要設置好,並且固定好了,如果你寫不滿那麼就會浪費,而且文件末尾處的”null“還需要處理下。
我認爲MappedByteBuffer更適合複製,修改類操作(當然讀也很快),這也是基於“將文件當作數組”思想。
public static void mappedFileChannel() throws Exception {
long start = System.currentTimeMillis();
System.out.println("writeBuffer-開始時間:"+start);
FileChannel fc = new RandomAccessFile("file7.txt","rw").getChannel();
byte[] b = "我們的家在東北的松花江上,我們的家在東北的松花江上,我們的家在東北的松花江上\n".getBytes("UTF-8");
int length= b.length;
System.out.println("字段大小:"+b.length);
long size = length*10000000;
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, size);
for(int i=0;i<10000000;i++) {
mbb.put(b);
}
fc.close();
long end = System.currentTimeMillis();
System.out.println("總消耗:"+(end-start));
}
以上。