最近把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));
}
以上。