5.java-nio-FileChannel常用方法

148 阅读5分钟

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的三种创建方式
  1. 通过文件流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();
            }
​
        }
​
​
    }
}
​