Channel
Channel特点
Channel对象可以异步的读写,每次读写都要依赖Buffer才能操作数据,所以有时候说Java NIO是面向缓冲区的。
Channel分类
FileChannel:面向文件的读写,文件读写和IO流一样是阻塞IO模式
DatagramChannel:面向UDP网络读写数据
SocketChannel:面向TCP网络读写数据
ServerSocketChannel:可以监听TCP连接,类似一个监听器,会为每个进来的连接创建一个SocketChannel,它本身不会传输数据
本节单独记录FileChannel,因为其与其他三个Channel不同,FileChannel是面向文件的读写,和IO流一样是阻塞IO模式,其他三个Channel面向网络,并支持NIO模型
FileChannel的三种创建方式
- 通过文件流FileInputStream/FileOutputStream提供的getChannel
FileInputStream inputStream = new FileInputStream("D:/test.txt");
FileChannel channel = inputStream.getChannel();
FileOutputStream outputStream = new FileOutputStream("D:/test.txt");
FileChannel channel1 = outputStream.getChannel();
2.** **通过RandomAccessFile的getChannel
RandomAccessFile randomAccessFile = new RandomAccessFile("./test.txt", "rw");
FileChannel channel2 = randomAccessFile.getChannel();
3.通过FileChannel.open静态方法,open比文件流、随机文件流的创建方式更具有操作模式上的选择性
OpenOption主要用于控制文件的操作模式:
READ:只读方式
WRITE:只写方式
APPEND:只追加方式
CREATE:创建新文件
CREATE_NEW:创建新文件,如果存在则失败
TRUNCATE_EXISTING:如果以读方式访问文件,它的长度将被清除至0
public static FileChannel open(Path path, OpenOption... options) throws IOException
public static FileChannel open(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException
//案例
Path path = FileSystems.getDefault().getPath("D:/test.txt");
FileChannel channel2 = FileChannel.open(path, StandardOpenOption.READ);
FileChannel常用方法
读写
public int read(ByteBuffer dst) throws IOException:从该通道的当前文件位置开始读取字节到缓冲区,并且用实际读取数更新读取下标指针的位置,如果读取完了返回-1
public final long read(ByteBuffer[] dsts) throws IOException :从该通道的当前文件位置开始读取字节到缓冲区,并且用实际读取数更新读取下标指针的位置,如果读取完了返回-1
将数据读取到指定的多个buffer容器中,将数据分散
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException:将数据读取到指定的多个buffer容器中,将数据分散,可以指定偏移量和读取长度
public abstract int write(ByteBuffer src) throws IOException: 将buffer容器的数据读取到通道中
public final long write(ByteBuffer[] srcs) throws IOException: 将多个buffer容器的数据读取到通道中,也叫数据聚合
public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException: 将多个buffer容器的数据读取到通道中,可以指定偏移量和写出长度
获取
public long size() throws IOException:获取关联文件的大小,如果该通道关闭会发生ClosedChannel异常
public long position() throws IOException:获取下标指针的位置,如果该通道关闭会发生ClosedChannel异常
public FileChannel truncate(long size) throws IOException:将此通道的文件截取指定大小,从文件开头开始,截取到指定大小
public void force(boolean metaData) throws IOException; 将通道里尚未写入磁盘的数据强制写到磁盘上,metaData参数表示写入时否包含元数据,如果为true则将文件本身的权限信息等也写入。
出于性能方面的考虑,操作系统在读文件时会将文件数据全部缓存在内存中,并拆分为多个内存块(linux上每块4KB),这种缓存行为叫做page cache。当多次使用这个文件时,不管是查看文件的一部分数据还是全部数据,都可以直接从内存中获取。
操作系统在写文件时会将文件数据缓存在内存中,但是还没有写入到磁盘。虽然在应用程序中,数据全部写入完毕了。但实际上文件数据可能还在内存中。正常情况下操作系统可以保证数据写入磁盘,但如果计算机突然断电。就会导致page cache内存数据丢失,所以channel提供了force方法,该方法底层是调用了系统函数fsync ( ),fsync系统函数会强制将page cache全部写入到磁盘,也叫刷盘。这也是channel为什么比outputStream更可靠的原因。
通道之间的数据传输
transferTo():零拷贝传输,具体使用请看第7章:零拷贝案例和原理
transferFrom():零拷贝传输,使用方式和transferTo相反,具体使用请看第7章:零拷贝案例和原理
FileChannel文件读写案例
import org.junit.Test;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelTest {
//写入文件到本地
@Test
public void test1() {
String data="你好hello123456";
FileOutputStream fileOutputStream=null;
try {
//1.使用输出流获取一个Channel
fileOutputStream=new FileOutputStream("C:\Users\test\Desktop\a.txt");
FileChannel channel = fileOutputStream.getChannel();
//2.创建一个和数据相同大小的byte容器
ByteBuffer buffer = ByteBuffer.allocate(data.getBytes().length);
//3.写入数据到容器中,此时position发生移动了
buffer.put(data.getBytes());
//4.将容器的数据读取处理写到Channel通道,调用flip翻转,指针重新归零后才能正确读取容器的数据
buffer.flip();
channel.write(buffer);//文本就写入到了a.txt中
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.直接关闭流
try {
if(fileOutputStream!=null){
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//读取本地文件到内存中
@Test
public void test2() {
FileInputStream fileInputStream=null;
try {
//1.使用读取流获取一个Channel
fileInputStream=new FileInputStream("C:\Users\test\Desktop\a.txt");//a.txt内容:你好hello123456
FileChannel channel = fileInputStream.getChannel();
//2.声明一个容量为6的byte容器
ByteBuffer buffer = ByteBuffer.allocate(6);
//3.循环读取文件,如果为-1表示读取到文件末,不为-1时返回读取的数量
while (channel.read(buffer)!=-1){
//将读取的字节转换为字符串
System.out.println(new String(buffer.array(),0,buffer.position(),"UTF-8"));
//重置position指针和limit,否则无法写入数据
buffer.clear();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.关闭流
try {
if(fileInputStream!=null){
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}