上一篇文章写了NIO中缓冲区(Buffer)的部分,这篇写通道(Channel)的部分。
首先呢,先说一下什么是通道。通道是一种类似输入输出流的东西,用来读取和写入数据,只是它不是直接操作通道,而是先读取到缓冲区,再通过缓冲区进行读取或写入。
通道本身是进行双向操作的,也就是既可以完成输入操作,还可以完成输出操作。
Channel本身是一个接口,接口里只定义了两个方法:
- isOpen():判断此通道是否是打开的
- close():关闭通道
平时常用的类主要是FileChannel类,这个类是进行文件读写操作的。
常用的方法有:
- read(ByteBuffer dst):将内容读入缓冲区
- write(ByteBuffer src):将内容从缓冲区写入到通道
- write(ByteBuffer src):将内容从缓冲区写入到通道
- write(ByteBuffer src):将内容从缓冲区写入到通道write(ByteBuffer src)将内容从缓冲区写入到通道
- close():关闭通道
- MappedByteBuffer map(FileChannel,MapMode mode,long position,long size):将通道的文件区域映射到内存中,同时指定映射模式,文件中的映射文件以及要映射的文件以及要映射的区域大小
要想使用FileChnnel类,可以通过FileInputStream或FileOutputStream类中getChannel()方法获取输入或输出的通道,先看一个例子:
public static void main(String[] args) throws IOException {
String[] info = { "你好", "之华", "hello", "zhihua" };// 待输出数据
File file = new File("d:/test.txt");// 要输出的文件
FileOutputStream fos = new FileOutputStream(file);// 实例化文件输出流
FileChannel fout = fos.getChannel();// 声明输出通道,并得到文件通道
ByteBuffer buffer = ByteBuffer.allocate(1024);// 开辟缓冲
for (int i = 0; i < info.length; i++) {// 循环将数据写入缓冲不过
buffer.put(info[i].getBytes("UTF-8"));
}
buffer.flip();// 重设缓冲区,准备输出
fout.write(buffer);// 输出
fout.close();
fos.close();
}
在打开D盘下test.txt文件之后就看到了
你好之华hellozhihua
在上面的例子,我们使用了输出通道,还用到了FIleOutputStream,但是我们讲了,通道是可以双向操作的,下面进行一下演示。现在D盘下创建两个文件,t1.txt和t2.txt
,并且在t1里面写入内容。运行如下代码
public static void main(String[] args) throws IOException {
File file1 = new File("d:/t1.txt");
File file2 = new File("d:/t2.txt");
FileInputStream fis = null;
FileOutputStream fos = null;
fis = new FileInputStream(file1);
fos = new FileOutputStream(file2);
java.nio.channels.FileChannel fin = null;
java.nio.channels.FileChannel fout = null;
fin = fis.getChannel();
fout = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while ((fin.read(buffer)) != -1) {
buffer.flip();// 重置缓冲区
fout.write(buffer);// 从缓冲区写入磁盘
buffer.clear();
}
fin.close();
fout.close();
fis.close();
fos.close();
System.out.println("finish!");
}
这时打开t2.txt,发现t2.txt中的内容已经和t1.txt中的内容是一样的了,也就是表明成功将数据存入到了t2.txt中。
通过FileChannel,还可以进行内存映射的操作。内存映射,是将文件内容存入内存,在内存里对数据进行操作,这样处理速度就会远快于在磁盘中进行操作。
要将文件映射到内存中,调用的是FileChannel类提供的 map() 方法。同样写个例子,先在D盘建一个xxx.txt文件,往里面写些内容。
public static void main(String[] args) throws IOException {
File file = new File("d:/xxx.txt");// 定义一个文件对象
FileInputStream fis = new FileInputStream(file);// 定义文件输入流
FileChannel fin = fis.getChannel();// 根据文件输入流获得文件通道对象
MappedByteBuffer mbb = fin.map(FileChannel.MapMode.READ_WRITE, 0,
file.length());// 设置内存映射模式为制度,起始位置为0,结束位置为file.length
byte data[] = new byte[(int) file.length()];// 新建一个文件大小的byte数组
int i = 0;// 设置下标
while (mbb.hasRemaining()) {
data[i++] = mbb.get();// 通过内存映射获得每个byte数组的值,并复制给data中相应位置
}
System.out.println(new String(data, "utf-8"));
fin.close();
fis.close();
}
这里map()方法的返回值是MappedByteBuffer类型的对象,它是一个专门用来做内存映射的Buffer,打印的结果是
春花秋月何时了,
往事知多少,
小楼昨夜又东风,
故国不堪回首月明中。
雕镂玉砌应犹在,
只是朱颜改,
问君能有几多愁,
恰似一江春水向东流。
这是正是我写入文件中的内容。在代码里可以看到map()方法的第一个参数为FileChannel.MapMode.READ_ONLY 这是一种内存映射模式,为只读模式。除此之外还有另外两种模式:
FileChannel.MapMode.PRIVATE:专用(写入时复制)映射模式
FileChannel.MapMode.READ_WRITE:读取/写入映射模式
另外需要注意,使用内存映射可能会导致磁盘上的内容也跟着改变,所以使用的时候要慎重。
通道还支持对文件进行锁定,即文件锁的功能。文件锁需要用到另一个类FileLock ,下面也是看代码
public static void main(String[] args) throws IOException,
InterruptedException {
File file = new File("d:/xxx.txt");
FileOutputStream fos = new FileOutputStream(file);
FileChannel fout = fos.getChannel();
FileLock lock = fout.tryLock();
if (lock != null) {
System.out.println("锁定文件");
Thread.sleep(3000);
lock.release();
System.out.println("解锁文件");
}
fout.close();
fos.close();
}
出现的结果就是在打印“锁定文件”3秒之后,打印了“解锁文件”。也就是在将文件锁定之后将其释放。当文件在一个线程里被锁定之后,别的线程就无法再对该文件进行操作了。