NIO学习笔记(六)FileChannel类API的使用
NIO学习笔记(六)FileChannel类API的使用
FileChannel的主要作用是读取、写入、映射和操作文件的通道。该通道永远是阻塞操作的。
FileChannel在内部维护了当前文件的position,可以对其进行查询和修改。该文件本身包含一个可读写、长度可变的字节序列,并且可以查询该文件的当前大小。当写入的字节超出文件的当前大小,则增加文件的大小;截取该文件的时候,则减小文件的大小。文件可能还有某个相关联的元数据,如访问权限、内容类型和最后的修改时间,但此类未定义访问元数据的方法。
除了字节通道常见的读取和写入、关闭操作外,此类还定义了下列特定于文件的操作:
- 以不影响通道当前位置的方式,对文件中的绝对位置的字节进行读取和写入。
- 将文件中的某个区域直接映射到内存中。对于较大的文件,这通常比调用普通的read和write更加高效。
- 强制对底层的存储设备进行文件的更新,确保在系统崩溃时不丢失数据。
- 可以锁定某个文件区域,以阻止其他程序对其访问。
- 以一种可被很多操作系统优化为直接向文件系统缓存发送或从中读取的高速传输方法,将字节从文件传授到某个通道,反之亦然。
1、写操作与位置使用
int write(ByteBuffer src)方法的作用是将remaining字节序列从给定的缓冲区中写入此通道的当前位置,此方法的行为与WritableByteChannel接口所指的行为完全一样;在给定的任一时刻,一个可写入的通道上只能进行一个写入操作,那么在第一个操作完成之前,将阻塞其他试图发起另一个写入操作的线程。该方法返回值代表写入的字节数,可能为0.
这个方法有两个特点:
- 将一个ByteBuffer缓冲区的remaining字节序列写入通道的当前位置。
- write(ByteBuffer)本身是一个同步方法。
使用示例:
public class FileChannelWriteDemo {
private static FileOutputStream fos;
private static FileChannel fileChannel;
public static void main(String[] args) throws IOException, InterruptedException {
fos = new FileOutputStream(new File("C:\\Users\\dell\\Desktop\\aa.txt"));
fileChannel = fos.getChannel();
for(int i = 0 ; i < 10 ; i ++){
new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.wrap("abc\r\n".getBytes());
try {
fileChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.wrap("123\r\n".getBytes());
try {
fileChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(3000);
fileChannel.close();
fos.close();
}
}
2、读操作
int read(ByteBuffer src)方法的作用将字节序列从此通道的当前位置读入给定的缓冲区的位置。此方法的行为与ReadableByteChannel接口中指定的行为完全一致,在任意给定时刻,一个可读通道上只能进行一个读取操作。如果某个线程在通道上发起读取操作,那么在第一个操作完成之前,将阻塞其他所有试图发起读取操作的线程。其他类型的IO操作是否继续与读取操作并发执行,取决于该通道的类型。该方法的返回值代表读取的字节数,可能为0。如果该通道达到末尾,则返回-1。
这个方法有两个特点:
- 将通道当前位置的字节序列读取到一个ByteBuffer缓冲区的remaining空间中。
- read(ByteBuffer)本身是一个同步方法。
示例代码:
public class ReadDemo {
private static FileInputStream fos;
private static FileChannel fileChannel;
public static void main(String[] args) throws IOException {
fos = new FileInputStream(new File("C:\\Users\\dell\\Desktop\\aa.txt"));
fileChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(5);
int readLength = fileChannel.read(buffer);
//查看一下返回值是什么
System.out.println(readLength);
//如果返回值为0,因为buffer的remaining为0
readLength = fileChannel.read(buffer);
System.out.println(readLength);
buffer.clear();
readLength = fileChannel.read(buffer);
//达到流的末尾,他的值为-1
System.out.println(readLength);
buffer.clear();
fileChannel.close();
fos.close();
}
}
3、批量写操作
long write(ByteBuffer[] srcs)方法的作用是将每个缓冲区的remaining字节序列写入此通道的当前位置。调用此方法的形式为c.write(srcs),该调用与c.write(srcs,0,srcs.length)一样。该方法实现的是GatherByteChannel接口中的同命名方法,之前介绍过这个接口,这里我就不多提了。
该方法呢有以下特性:
- 讲一个ByteBuffer缓冲区中的remaining字节序列写入通道的当前位置。
- write(ByteBuffer)方法是同步的。
接下来我们来看一个例子,顺便验证一下他的同步性
public class SyncWriteDemo {
private static FileOutputStream fos;
private static FileChannel fileChannel;
public static void main(String[] args) throws IOException, InterruptedException {
fos = new FileOutputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
fileChannel = fos.getChannel();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer1 = ByteBuffer.wrap("odhvuwenive".getBytes());
ByteBuffer buffer2 = ByteBuffer.wrap("ewhvownvebw".getBytes());
ByteBuffer[] bb = new ByteBuffer[]{buffer1,buffer2};
try {
fileChannel.write(bb);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer1 = ByteBuffer.wrap("aaaaaaaaaaa".getBytes());
ByteBuffer buffer2 = ByteBuffer.wrap("bbbbbbbbbbb".getBytes());
ByteBuffer[] bb = new ByteBuffer[]{buffer1,buffer2};
try {
fileChannel.write(bb);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(3000);
fileChannel.close();
fos.close();
}
}
4、批量读操作
long read(ByteBuffer[] srcs)方法的作用是将字节序列从此通道读入给定的缓冲区数组中的第0个缓冲区的当前位置。调用此方法的形式为c.read(dsts),该调用与调用c.read(dsts,0,dsts.length)的形式完全相同。该方法的实现是ScatteringByteChannel接口中的同名方法。该方法有以下特性:
- 将通道当前位置的字节序列读入第一个ByteBuffer缓冲区的remaining空间中
- read(ByteBuffer[] srcs)方法是同步的
接下来我们来看一个例子
public class SyncReadDemo {
private static FileInputStream fos;
private static FileChannel fileChannel;
public static void main(String[] args) throws IOException, InterruptedException {
fos = new FileInputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
fileChannel = fos.getChannel();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer1 = ByteBuffer.allocate(8);
ByteBuffer buffer2 = ByteBuffer.allocate(8);
ByteBuffer[] bb = new ByteBuffer[]{buffer1,buffer2};
try {
long readlength = fileChannel.read(bb);
while (readlength != -1){
synchronized (SyncReadDemo.class){
System.out.println(Arrays.toString(bb));
}
buffer1.clear();
buffer2.clear();
readlength = fileChannel.read(bb);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer1 = ByteBuffer.allocate(8);
ByteBuffer buffer2 = ByteBuffer.allocate(8);
ByteBuffer[] bb = new ByteBuffer[]{buffer1,buffer2};
try {
long readlength = fileChannel.read(bb);
while (readlength != -1){
synchronized (SyncReadDemo.class){
System.out.println(Arrays.toString(bb));
}
buffer1.clear();
buffer2.clear();
readlength = fileChannel.read(bb);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Thread.sleep(3000);
fileChannel.close();
fos.close();
}
}
5、部分批量写操作
long wirte(ByteBuffer[] srcs,int offset , int length)方法的作用是以指定缓冲区数组的offset下标开始,向后使用length个字节缓冲区,再讲每个缓冲区的remaining剩余字节子序列写入此通道的当前位置。该方法实现的是GatheringByteChannel的接口中的同名方法,该方法有以下特性:
- 将1个ByteBuffer缓冲区的remaining字节序列写入通道的当前位置
- write方法是同步的
接下来我们来看一个例子
public class PartWriteDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
FileChannel fileChannel = fos.getChannel();
ByteBuffer buffer1 = ByteBuffer.wrap("odhvuwenive".getBytes());
ByteBuffer buffer2 = ByteBuffer.wrap("ewhvownvebw".getBytes());
ByteBuffer[] bb = new ByteBuffer[]{buffer1,buffer2};
fileChannel.write(bb,0,2);
fileChannel.close();
fos.close();
}
}
6、部分批量读操作
long read(ByteBuffer[] srcs,int offset , int length)方法的作用是将通道当前位置的字节序列读入以下标为offset开始的ByteBuffer[]数组中的remianing剩余空间,并且连续写入length个ByteBuffer空间。该方法实现的是ScatteringByteChannel的接口中的同名方法,该方法有以下特性:
- 将通道的当前位置的字节序列读入1个ByteBuffer缓冲区的remaining
- write方法是同步的
接下来我们来看一个例子
public class PartReadDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
FileChannel fileChannel = fis.getChannel();
ByteBuffer buffer1 = ByteBuffer.allocate(2);
ByteBuffer buffer2 = ByteBuffer.allocate(2);
ByteBuffer[] bb = new ByteBuffer[]{buffer1,buffer2};
long readlength = fileChannel.read(bb,0,2);
System.out.println(readlength);
System.out.println(bb);
fileChannel.close();
fis.close();
}
}
7、向通道的指定position位置写入数据
write(ByteBuffer src , long position)方法的作用是将缓冲区src的remaining字节序列写入通道的指定位置position。除了从给定的文件位置开始写入各个字节,而不是从该通道的当前位置外此方法的执行方式与write(ByteBuffer)大体一致。
接下来我们来看一个小小例子
public class WrietPosition {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
FileChannel fileChannel = fos.getChannel();
ByteBuffer buffer1 = ByteBuffer.wrap("odhvuwenive".getBytes());
fileChannel.write(buffer1,2);
fileChannel.close();
fos.close();
}
}
8、读取通道指定位置的数据
read(ByteBuffer src , long position)方法的作用是将通道指定位置的字节序列读入给定的缓冲区的当前位置。
下面我们来看一个例子
public class ReadPosition {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
FileChannel fileChannel = fis.getChannel();
ByteBuffer buffer1 = ByteBuffer.allocate(2);
long readlength = fileChannel.read(buffer1,2);
System.out.println(readlength);
fileChannel.close();
fis.close();
}
}
9、设置位置与获取大小
position(long newposition)方法的作用是设置此通道的文件位置。将该位置设为大于文件当前大小的值是合法的,但这不会更新文件的大小,稍后试图在这样的位置读取字节将立即返回已到达文件末尾的指示,稍后试图在这个位置写入字节将导致文件扩大,以容纳新的字节,在以前的文件末尾和新写入字节之间的字节值是未规定的。
long size()方法的作用是返回此通道关联文件的当前大小。
public class Position {
public static void main(String[] args) throws IOException {
ByteBuffer buffer1 = ByteBuffer.wrap("odhvuwenive".getBytes());
ByteBuffer buffer2 = ByteBuffer.wrap("ewhvownvebw".getBytes());
FileOutputStream fos = new FileOutputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
FileChannel channel = fos.getChannel();
System.out.println("position = "+channel.position()+" , size = "+channel.size());
channel.write(buffer1);
System.out.println("position = "+channel.position()+" , size = "+channel.size());
channel.position(2);
System.out.println("position = "+channel.position()+" , size = "+channel.size());
channel.write(buffer2);
System.out.println("position = "+channel.position()+" , size = "+channel.size());
channel.close();
fos.flush();
fos.close();
}
}
10、截断缓冲区
truncate(long size)方法的作用是将此通道的文件截取为给定的大小。如果给定的大小小于该文件的大小,则截取该文件,丢弃文件新末尾后面的所有字节。如果给定大小大于或等于该文件的当前大小,则不修改文件。无论是哪种情况,如果此通道的文件位置大于给定大小,则将位置设置为该大小。下面我们看一段代码:
//如果给定大小大于或等于该文件的当前的大小,则不修改文件
public class Truncate {
public static void main(String[] args) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap("xiaosd".getBytes());
FileOutputStream fos = new FileOutputStream(new File("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt"));
FileChannel channel = fos.getChannel();
channel.write(buffer);
System.out.println("position = "+channel.position()+" , size = "+channel.size());
channel.truncate(3000);
System.out.println("position = "+channel.position()+" , size = "+channel.size());
channel.close();
fos.flush();
fos.close();
}
}
11、将数据传输到其他可写入字节通道
long transferTo(position,count,WritableByteChannel dest)方法的作用是将字节从此通道的文件传输到给定的可写入字节通道。transferTo()的功能相当于write()方法,只不过是将通道中的数据传输到另一个通道中,而不是缓冲区中。试图读取从此通道position开始的count个字节,并将其写入目标通道的当前位置。那么会发生以下几种情况
- 如果给定的位置大于该文件的当前大小,则不传输任何字节。
- 如果count的字节个数大于position到size之间的个数,则传输通道的size-position个字节数到目标通道。
- 如果count的字节数小于position到size之间的个数,则传输通道的count个字节数到目标通道。
下面来看一个例子
public class TransferTo {
public static void main(String[] args) throws IOException {
RandomAccessFile file1 = new RandomAccessFile("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt","rw");
RandomAccessFile file2 = new RandomAccessFile("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\bb.txt","rw");
FileChannel fileChannel1 = file1.getChannel();
FileChannel fileChannel2 = file2.getChannel();
System.out.println("position = "+fileChannel2.position());
fileChannel1.transferTo(1,5,fileChannel2);
System.out.println("position = "+fileChannel2.position());
fileChannel1.close();
fileChannel2.close();
file1.close();
file2.close();
}
}
12、将字节给定可读取字节通道传输到此通道的文件中
long transferFrom(ReadableByteChannel src , position ,count )方法的作用是将字节从给定的可读取的字节通道传输到此通道的文件中。transferFrom()方法的功能相当于read()方法,只不过是将通道中的数据传输到另一个通道,而不是缓存中。试着从源通道中最多读取count个字节,并将其写入到此通道中的文件中给定position处开始的位置。此方法的调用不一定传输所有请求的字节;是否传输取决于通道的性质和状态。如果源通道的剩余空间小于count个字节,或者如果源通道是非阻塞的,并且其输入缓冲区中可以直接可用的空间小于count个字节,则所传输的字节数要小于请求的字节数。
public class TransferToFrom {
public static void main(String[] args) throws IOException {
RandomAccessFile file1 = new RandomAccessFile("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\aa.txt","rw");
RandomAccessFile file2 = new RandomAccessFile("F:\\Idea-Code\\NIO-Demo\\src\\com\\xiao\\nio\\channel\\bb.txt","rw");
FileChannel fileChannel1 = file1.getChannel();
FileChannel fileChannel2 = file2.getChannel();
fileChannel2.position(4);
System.out.println("position = "+fileChannel2.position());
fileChannel1.transferFrom(fileChannel2,3,2);
System.out.println("position = "+fileChannel2.position());
fileChannel1.close();
fileChannel2.close();
file1.close();
file2.close();
}
}