Java NIO学习(二):Channel通道

简介: Java NIO 的通道类似流,但又有些不同:

2.1 Channel 概述



Java NIO 的通道类似流,但又有些不同:


  • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
  • 通道可以异步地读写。
  • 通道中的数据总是要先读到一个 Buffer,或者总是要从一个 Buffer 中写入。


2.2 Channel 实现



下面是 Java NIO 中最重要的Channel 的实现:


  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel


  1. FileChannel 从文件中读写数据。
  2. DatagramChannel 能通过 UDP 读写网络中的数据。
  3. SocketChannel 能通过 TCP 读写网络中的数据。
  4. ServerSocketChannel 可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel。正如你所看到的,这些通道涵盖了 UDP 和 TCP 网络 IO,以及文件 IO


2.3 FileChannel 介绍和示例



FileChannel 类可以实现常用的 read,write 以及scatter/gather 操作,同时它也提供了很多专用于文件的新方法。这些方法中的许多都是我们所熟悉的文件操作。


d314a0b9e1eb4958e12c84bcb183f5f4.png

下面是一个使用 FileChannel 读取数据到 Buffer 中的示例:


public class FileChannelDemo1 {
    //FileChannel读取数据到buffer中
    public static void main(String[] args) throws Exception {
        //创建FileChannel
        RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu\\01.txt","rw");
        FileChannel channel = aFile.getChannel();
        //创建Buffer
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //读取数据到buffer中
        int bytesRead = channel.read(buf);
        while(bytesRead != -1) {
            System.out.println("读取了:"+bytesRead);
            buf.flip();
            while(buf.hasRemaining()) {
                System.out.println((char)buf.get());
            }
            buf.clear();
            bytesRead = channel.read(buf);
        }
        aFile.close();
        System.out.println("结束了");
    }
}


2.4 FileChannel 操作详解



2.4.1 打开 FileChannel


在使用FileChannel 之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个 InputStream、OutputStream 或RandomAccessFile来获取一个 FileChannel 实例。下面是通过 RandomAccessFile 打开 FileChannel 的示例:


// 打开FileChannel
RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu\\001.txt","rw");
FileChannel channel = aFile.getChannel();


2.4.2 从FileChannel 读取数据


调用多个read()方法之一从 FileChannel 中读取数据。如:


ByteBuffer buf =ByteBuffer.allocate(48); 
int bytesRead = inChannel.read(buf);


首先,分配一个Buffer。从 FileChannel 中读取的数据将被读到Buffer 中。然后,调用FileChannel.read()方法。该方法将数据从 FileChannel 读取到 Buffer 中。read() 方法返回的 int 值表示了有多少字节被读到了 Buffer 中。如果返回-1,表示到了文件末尾。


2.4.3 向FileChannel 写数据


使用FileChannel.write()方法向 FileChannel 写数据,该方法的参数是一个 Buffer。如:


public class FileChannelDemo2 {
    public static void main(String[] args) throws Exception {
        // 打开FileChannel
        RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu\\001.txt","rw");
        FileChannel channel = aFile.getChannel();
        //创建buffer对象
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        String newData = "data atguigu";
        buffer.clear();
        //写入内容
        buffer.put(newData.getBytes());
        buffer.flip();
        //FileChannel完成最终实现
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }
        //关闭
        channel.close();
    }
}


注意FileChannel.write()是在 while 循环中调用的。因为无法保证 write()方法一次能向 FileChannel 写入多少字节,因此需要重复调用 write()方法,直到 Buffer 中已经没有尚未写入通道的字节。


2.4.4 关闭 FileChannel


用完FileChannel 后必须将其关闭。如:


inChannel.close();


2.4.5 FileChannel 的 position 方法


有时可能需要在FileChannel 的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取 FileChannel 的当前位置。也可以通过调用 position(longpos)方法设置 FileChannel 的当前位置。这里有两个例子:


long pos = channel.position(); 
channel.position(pos +123);


如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回1(文件结束标志)。


如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。


2.4.6 FileChannel 的 size 方法


FileChannel 实例的size()方法将返回该实例所关联文件的大小。如:


longfileSize = channel.size();


2.4.7 FileChannel 的 truncate 方法


可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:


channel.truncate(1024);


这个例子截取文件的前 1024 个字节。


2.4.8 FileChannel 的 force 方法


FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到 FileChannel 里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。


force()方法有一个 boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。


2.4.9 FileChannel 的 transferTo 和 transferFrom 方法


主要用于通道之间的数据传输:如果两个通道中有一个是 FileChannel,那你可以直接将数据从一个 channel 传输到另外一个 channel。


2.4.9.1 transferFrom()方法


FileChannel 的 transferFrom()方法可以将数据从源通道传输到 FileChannel 中(这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个FileChannel 完成文件间的复制的例子:


//通道之间数据传输
public class FileChannelDemo3 {
    //transferFrom()
    public static void main(String[] args) throws Exception {
        // 创建两个fileChannel
        RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu\\001.txt","rw");
        FileChannel fromChannel = aFile.getChannel();
        RandomAccessFile bFile = new RandomAccessFile("d:\\atguigu\\02.txt","rw");
        FileChannel toChannel = bFile.getChannel();
        //fromChannel 传输到 toChannel
        long position = 0;
        long size = fromChannel.size();
        toChannel.transferFrom(fromChannel,position,size);
        aFile.close();
        bFile.close();
        System.out.println("over!");
    }
}


方法的输入参数 position 表示从 position 处开始向目标文件写入数据,count 表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。此外要注意,在SoketChannel 的实现中,SocketChannel 只会传输此刻准备好的数据(可能不足 count 字节)。因此,SocketChannel 可能不会将请求的所有数据(count 个字节)全部传输到FileChannel 中。


2.4.9.2 transferTo()方法


transferTo()方法将数据从FileChannel 传输到其他的 channel 中。


public class FileChannelDemo4 {
    //transferTo()
    public static void main(String[] args) throws Exception {
        // 创建两个fileChannel
        RandomAccessFile aFile = new RandomAccessFile("d:\\atguigu\\001.txt","rw");
        FileChannel fromChannel = aFile.getChannel();
        RandomAccessFile bFile = new RandomAccessFile("d:\\atguigu\\03.txt","rw");
        FileChannel toChannel = bFile.getChannel();
        //fromChannel 传输到 toChannel
        long position = 0;
        long size = fromChannel.size();
        fromChannel.transferTo(0,size,toChannel);
        aFile.close();
        bFile.close();
        System.out.println("over!");
    }
}


2.5 Scatter/Gather


Java NIO 开始支持 scatter/gather,scatter/gather 用于描述从 Channel 中读取或者写入到Channel 的操作。


分散(scatter):从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。因此,Channel 将从 Channel 中读取的数据“分散(scatter)”到多个 Buffer 中。


聚集(gather):写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个 Channel,因此,Channel 将多个 Buffer 中的数据“聚集(gather)”后发送到 Channel。


scatter/ gather 经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的 buffer 中,这样你可以方便的处理消息头和消息体。


2.5.1 Scattering Reads


Scattering Reads 是指数据从一个 channel 读取到多个 buffer 中。


ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body    = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);


注意 buffer 首先被插入到数组,然后再将数组作为 channel.read() 的输入参数。 read()方法按照 buffer 在数组中的顺序将从 channel 中读取的数据写入到 buffer,当一个 buffer 被写满后,channel 紧接着向另一个 buffer 中写。


Scattering Reads 在移动下一个 buffer 前,必须填满当前的 buffer,这也意味着它不适用于动态消息(消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads 才能正常工作。


2.5.2 Gathering Writes


Gathering Writes 是指数据从多个 buffer 写入到同一个 channel。


ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body    = ByteBuffer.allocate(1024);
//writedataintobuffers
ByteBuffer[] bufferArray = { header, body }; channel.write(bufferArray);

buffers 数组是 write()方法的入参,write()方法会按照 buffer 在数组中的顺序,将数据写入到channel,注意只有 position 和 limit 之间的数据才会被写入。因此,如果一个 buffer 的容量为 128byte,但是仅仅包含 58byte 的数据,那么这 58byte 的数据将被写入到 channel 中。因此与 Scattering Reads 相反,Gathering Writes 能较好的处理动态消息。


相关文章
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
97 43
Java学习十六—掌握注解:让编程更简单
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
2月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
39 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
57 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
2月前
|
前端开发 Java 应用服务中间件
Javaweb学习
【10月更文挑战第1天】Javaweb学习
34 2
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
41 1
|
2月前
|
存储 搜索推荐 算法
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
29 1
|
2月前
|
存储 算法 Java
带你学习java的数组军队列
带你学习java的数组军队列
36 0
|
2月前
|
Java 大数据 开发工具
java学习——环境准备(1)
java学习——环境准备(1)
42 0