源文出处:http://examples.javacodegeeks.com/core-java/nio/filechannel/java-nio-channels-filechannel-example/
本文主要介绍FileChannel类和它的简单使用(使用FileChannel读写文件)。该类作为Java NIO(New IO)File API的一部分,从Java1.4开始支持。
注意:本文中的范例代码编译并运行在Windows OS 和 Java SE 7上。
1. 介绍
NIO使得Java程序员不需要写过多的Code就可以实现高性能的I/O。NIO把最消耗性能的操作(例如:filling 和 draining buffers)交给了操作系统,因此在JavaSE5, 6, 7版本上 NIO(和IO)的性能有了提高。
Buffer和Channel是NIO的主要概念。
2. Buffers and Channels
Buffer提供了一套可以存储固定数量原始数据元素的内存容器。在NIO中,所有的数据都是用Buffer处理。读数据时,把数据直接读入Buffer。写数据时,同样也把数据写入Buffer。Buffer搭配Channel一起使用。Channel是I/O传输发生的入口,Buffer是数据传输的源头或是目的地。
3. 使用FileChannel读写文件
Filechannel是阻塞的读写管道. 它可以用于读,写,mapping和manipulating 一个文件. FileChannel 对象是线程安全的。一个FileChannel 实例可以通过RandomAccessFile, FileInputStream或 FileOutputStream类中的getChannel()方法得到,或是通过FileChannel的静态open()方法获取。本文会介绍这两种获取FileChannel 对象的方法。
4. 读文件
这个例子介绍了使用FileChannel读一个文件进入Buffer并打印Buffer内容。
4.1. 输入文件
一个文本文件,内容:1234567890
4.2.创建FileChannel对象
FileChannel类中的静态方法open()用于创建一个管道。该方法打开一个文件,返回一个可以访问指定文件的FileChannel对象。
Path path = Paths.get("readfile.txt");
FileChannel fileChannel = FileChannel.open(path);
4.3. 创建一个Buffer
使用ByteBuffer的allocate()静态方法创建一个ByteBuffer 对象。这个像Buffer的位置是0,它的limit是它的容量,它的元素将被初始化为0。在本例中,初始化的容量为6.
ByteBuffer buffer = ByteBuffer.allocate(6);
4.4. 从管道中读入Buffer
FileChannel的read()方法读一组字节进入所给的Buffer。该方法返回所读字节的数量,如果管道已经到了流的末尾返回-1.
int noOfBytesRead = fileChannel.read(buffer);
从管道的当前位置(初始值为0)读取字节,根据实际读取的字节数更新文件的位置(例如:在读完一次后位置为6)。管道的position()方法返回当前的位置。ByteBuffer 也有一个position()方法。初始值为0。读完一次后,该值为6。ByteBuffer 的flip()方法是的ByteBuffer 可以为get操作准备好数据序列:它设置limit为当前的position(在本例中为6)然后设置position为0.
buffer.flip();
4.5. 打印Buffer中的数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
ByteBuffer 的clear()方法可以为channel-read操作准备好数据序列,它设置limit为capacity(6)并设置position为0.
buffer.clear();
4.6. 关闭管道
FileChannel的close()方法可以关闭管道。
异常:
在这个例子中, FileChannel的open(), close(), position() 和read() 方法抛 IOException
下面是完整的代码:
import java.io.file.Paths;
import java.nio.file.Path;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.io.FileReader;
import java.io.BufferedReader;
public class FileChannelRead {
public static void main (String [] args)
throws Exception {
new FileChannelRead().readFile();
}
private void readFile()
throws IOException {
String filePath = "readfile.txt";
printFileContents(filePath);
Path path = Paths.get(filePath);
FileChannel fileChannel = FileChannel.open(path);
ByteBuffer buffer = ByteBuffer.allocate(6);
int noOfBytesRead = fileChannel.read(buffer);
while (noOfBytesRead != -1) {
System.out.println("Number of bytes read: " + noOfBytesRead);
buffer.flip();
System.out.print("Buffer contents: ");
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
System.out.println(" ");
buffer.clear();
noOfBytesRead = fileChannel.read(buffer);
}
fileChannel.close();
}
private void printFileContents(String path)
throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
String textRead = br.readLine();
System.out.println("File contents: ");
while (textRead != null) {
System.out.println(" " + textRead);
textRead = br.readLine();
}
fr.close();
br.close();
}
}
输出是:
File contents: 1234567890
Number of bytes read: 6
Buffer contents: 123456
Number of bytes read: 4
Buffer contents: 7890
输出:
文件内容: 1234567890;
读取的字数数量: 6, 缓冲内容: 123456. 这个第一次读。前6个字节从管道读入缓冲,缓冲中的内容是 123456。
注意: 这个点缓冲和管道的位置是6。缓冲的flip()的方法把位置改为0。然后读取的字节数量为4,缓冲内容为7890。这是第二次的读取,也是最后一次。有四个字数被读取并打印。
注意: 现在管道的位置为 10,缓冲的位置为 4
5. 写文件
这个例子给出了通过管道从缓存中读数据并写文件的步骤,最后打印文件的内容。
5.1. 输入
输入是一个字符串。这个字符串被转换成字节数组,然后赋给buffer。
String input = "file channel example";
byte [] inputBytes = input.getBytes();
5.2. 创建buffer:
FileChannel的wrap()静态方法可以把自己数组放入一个buffer。这个新的buffer的capacity和limit是数组的长度,它的初始position是0.
ByteBuffer buffer = ByteBuffer.wrap(inputBytes);
5.3. 创建管道:
FileOutputStream的getChannel()方法可以创建管道。这个方法返回一个连着这个文件的管道。
String filePath = "writefile.txt";
FileOutputStream fos = new FileOutputStream(filePath);
FileChannel fileChannel = fos.getChannel();
注意:这个例子中,开始文件并不存在。上面的代码块会创建它。
5.4. 把Buffer中的数据写入管道连接的文件
FileChannel的write() 方法可以把buffer中的字节数组写入这个管道。从管道中当前文件的位置开始写入字节(这里position为0)。
int noOfBytesWritten = fileChannel.write(buffer);
5.5. 关闭管道:
关闭文件管道和文件输出流:
fileChannel.close();
fos.close();
5.6. 打印文件内容:
打印文件内容到终端输出。
异常:
FileOutputStream的构造方法抛 FileNotFoundException.
FileChannel的 write(), close()方法和FileOutputStream的 close() 方法抛IOException.
下面是使用管道写文件的完整代码块:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.io.FileReader;
import java.io.BufferedReader;
public class FileChannelWrite {
public static void main (String [] args)
throws Exception {
new FileChannelWrite().writeFile();
}
private void writeFile()
throws IOException {
String input = "file channel example";
System.out.print("Input string: " + input);
byte [] inputBytes = input.getBytes();
ByteBuffer buffer = ByteBuffer.wrap(inputBytes);
String filePath = "writefile.txt";
FileOutputStream fos = new FileOutputStream(filePath);
FileChannel fileChannel = fos.getChannel();
fileChannel.write(buffer);
fileChannel.close();
fos.close();
printFileContents(filePath);
}
private void printFileContents(String path)
throws IOException {
FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr);
String textRead = br.readLine();
System.out.println("File contents: ");
while (textRead != null) {
System.out.println(" " + textRead);
textRead = br.readLine();
}
fr.close();
br.close();
}
}
6. lock()和tryLock()方法
下面是lock()和tryLock()方法的使用总结:
Case1. 一个Java程序锁了这个文件,其它的程序(如写字板)不能修改或保存这个被锁的文件。
Case 2: 当一个软件 (e.g. Excel) 锁一个文件,如果你调用tryLock()方法, 将会抛FileNotFoundException
Case 3: 如果在同一个线程或是不同的线程中多次调用tryLock()方法,将抛 OverlappingFileLockException
Case 4: 当一个JVM调用lock()方法锁住一个文件,如果另一个JVM调用tryLock()/lock()方法锁一个文件,tryLock()方法会立即返回null,lock()方法会一直阻塞直到之前的JVM释放锁。
参考:http://blog.youkuaiyun.com/totogogo/article/details/3123117