New I/O
在JDK1.4版首次出现的 java.nio.* 包主要是以提高 I/O 速度为目标,之所以能优化 I/O 速度,是因为它的数据结构变得更贴近计算机输入输出的方式:
Channel 和 ByteBuffer,你可以把 Channel 想像成一个煤层,ByteBuffer是煤层和地表间不断运送煤的小推车。在Java 旧的 IO类中,FileInputStream, FileOutputStream, RandomAccessFile 这三类被修改成可以产生 FileChannel类,而 Reader 和 Writer 是不会产生 FileChannel 类的。范例代码如下:
private static void writeDataWithChannel(String path) {
FileChannel fo = null;
FileChannel ra = null;
FileChannel fi = null;
try {
fo = new FileOutputStream(path).getChannel();
fo.write(ByteBuffer.wrap("lol Test".getBytes()));
fo.close();
// /
ra = new RandomAccessFile(path, "rw").getChannel();
ra.position(ra.size());
ra.write(ByteBuffer.wrap(" -some more".getBytes()));
ra.position(0);
ByteBuffer bb = ByteBuffer.allocate(1024);
ra.read(bb);
bb.flip();
while (bb.hasRemaining()) {
System.out.print((char) bb.get());
}
System.out.print("\n");
ra.close();
// /
fi = new FileInputStream(path).getChannel();
ByteBuffer bb2 = ByteBuffer.allocate(2);
fi.read(bb2);
bb2.flip();
while (bb2.hasRemaining()) {
System.out.print((char) bb2.get());
}
System.out.println();
fi.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (fo != null) {
fo.close();
}
if (ra != null) {
ra.close();
}
if (fi != null) {
fi.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在读取文件时,我们必须调用 ByteBuffer.allocate()方法,并且用参数指定 ByteBuffer的大小,因为 nio 的设计目标是快速地移动大量的数据,因此 ByteBuffer的大小一般会大一些,1K(1024)可能还是小了些,至於得多大,可能还是得实际去调试,以取得 I/O 速度上的优势。如果我们要再次读取的话,我们需要调用 clear() 将ByteBuffer旧有的数据全部清除。
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("wrong cmd");
return;
}
copyFile2(args[0], args[1]);
}
private static void copyFile2(String originFile, String newFile) {
FileChannel fi = null;
FileChannel fo = null;
try {
fi = new FileInputStream(originFile).getChannel();
fo = new FileOutputStream(newFile).getChannel();
fi.transferTo(0, fi.size(), fo);
// or fo.transferFrom(in,0,in.size());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void copyFile(String originFile, String newFile) {
FileChannel fi = null;
FileChannel fo = null;
try {
fi = new FileInputStream(originFile).getChannel();
fo = new FileOutputStream(newFile).getChannel();
ByteBuffer bb = ByteBuffer.allocate(1024);
while (fi.read(bb) != -1) {
bb.flip();
fo.write(bb);
bb.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fo != null) {
fo.close();
}
if (fi != null) {
fi.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
甚至,更方便一点,可以使用 FileChannel 类的
transferTo() 及
transferFrom() 方法就更方便了!看之前的代码,我们必须自 ByteBuffer中逐个读 byte,然後强转为 char ,再打印出来,但实际上,我们有 CharBuffer 类,似乎是能将全部的字符用 toString() 方法打印出来,我们可以看看下面这个例子:
private static void readFileContent(String filePath2) {
FileChannel fo = null;
FileChannel fi = null;
try {
fo = new FileOutputStream(filePath2).getChannel();
fo.write(ByteBuffer.wrap("来玩玩 FileChannel ".getBytes()));
// /
fi = new FileInputStream(filePath2).getChannel();
ByteBuffer bb = ByteBuffer.allocate(1024);
fi.read(bb);
bb.flip();
// doesn't work
System.out.println("doesn't work: " + bb.asCharBuffer());
bb.rewind();
String encoding = System.getProperty("file.encoding");
System.out.println("System encoding: " + encoding);
System.out.println("Decoded using: " + encoding + " : "
+ Charset.forName(encoding).decode(bb));
fo.write(ByteBuffer.wrap("OMG 试看看".getBytes("UTF-16BE")));
bb.clear();
fi.read(bb);
bb.flip();
System.out.println(bb.asCharBuffer());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (fo != null) {
fo.close();
}
if (fi != null) {
fi.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上面的例子中,我们需要注意的是它的编码(encoding)和解码(decoding)的一致性,否则会出现乱码的情况,使用 Charset 类可以让我们将字符用许多不同的格式去编码 (encoding) ,此外 ByteBuffer 和网络传输数据一样,是采用 Big- Endian(这边有网上找到有关 little-endian和 big-endian的好文),ByteBuffer可以调用 order() 方法,将其设置为 big-endian 或 little-endian,在这边就不另外说明了~