Java NIO之Channel(通道)

蜡笔小新v 2021-07-20 04:18:44
java


 

Java NIO之Channel(通道)

Buffer(缓冲区)介绍

通常来说NIO中的所有IO都是从 Channel(通道) 开始的。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

数据读取和写入操作图示:
在这里插入图片描述

Java NIO Channel通道和流非常相似,主要有以下几点区别:

通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。
通道可以异步读写。
通道总是基于缓冲区Buffer来读写。

Java NIO中最重要的几个Channel的实现:

  • FileChannel: 用于文件的数据读写
  • DatagramChannel: 用于UDP的数据读写
  • SocketChannel: 用于TCP的数据读写,一般是客户端实现
  • ServerSocketChannel: 允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现

类层次结构:
下面的UML图使用Idea生成的。
在这里插入图片描述

FileChannel的使用

使用FileChannel读取数据到Buffer(缓冲区)以及利用Buffer(缓冲区)写入数据到FileChannel:

package filechannel;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelTxt {
public static void main(String args[]) throws IOException {
//1.创建一个RandomAccessFile(随机访问文件)对象,
RandomAccessFile raf=new RandomAccessFile("D:\\niodata.txt", "rw");
//通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。
FileChannel inChannel=raf.getChannel();
//2.创建一个读数据缓冲区对象
ByteBuffer buf=ByteBuffer.allocate(48);
//3.从通道中读取数据
int bytesRead = inChannel.read(buf);
//创建一个写数据缓冲区对象
ByteBuffer buf2=ByteBuffer.allocate(48);
//写入数据
buf2.put("filechannel test".getBytes());
buf2.flip();
inChannel.write(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。
buf.flip();
//如果还有未读内容
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
//清空缓存区
buf.clear();
bytesRead = inChannel.read(buf);
}
//关闭RandomAccessFile(随机访问文件)对象
raf.close();
}
}

运行效果:
在这里插入图片描述
通过上述实例代码,我们可以大概总结出FileChannel的一般使用规则:

1. 开启FileChannel

使用之前,FileChannel必须被打开 ,但是你无法直接打开FileChannel(FileChannel是抽象类)。需要通过 InputStream , OutputStream 或 RandomAccessFile 获取FileChannel。
我们上面的例子是通过RandomAccessFile打开FileChannel的:

//1.创建一个RandomAccessFile(随机访问文件)对象,
RandomAccessFile raf=new RandomAccessFile("D:\\niodata.txt", "rw");
//通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。
FileChannel inChannel=raf.getChannel();

2. 从FileChannel读取数据/写入数据
从FileChannel中读取数据/写入数据之前首先要创建一个Buffer(缓冲区)对象,Buffer(缓冲区)对象的使用我们在上一篇文章中已经详细说明了,如果不了解的话可以看我的上一篇关于Buffer的文章。

使用FileChannel的read()方法读取数据:

//2.创建一个读数据缓冲区对象
ByteBuffer buf=ByteBuffer.allocate(48);
//3.从通道中读取数据
int bytesRead = inChannel.read(buf);

使用FileChannel的write()方法写入数据:

 //创建一个写数据缓冲区对象
ByteBuffer buf2=ByteBuffer.allocate(48);
//写入数据
buf2.put("filechannel test".getBytes());
buf2.flip();
inChannel.write(buf);

3. 关闭FileChannel

完成使用后,FileChannel您必须关闭它。

channel.close();

SocketChannel和ServerSocketChannel的使用

利用SocketChannel和ServerSocketChannel实现客户端与服务器端简单通信:
SocketChannel 用于创建基于tcp协议的客户端对象,因为SocketChannel中不存在accept()方法,所以,它不能成为一个服务端程序。通过 connect()方法 ,SocketChannel对象可以连接到其他tcp服务器程序。
客户端:

package socketchannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class WebClient {
public static void main(String[] args) throws IOException {
//1.通过SocketChannel的open()方法创建一个SocketChannel对象
SocketChannel socketChannel = SocketChannel.open();
//2.连接到远程服务器(连接此通道的socket)
socketChannel.connect(new InetSocketAddress("127.0.0.1", 3333));
// 3.创建写数据缓存区对象
ByteBuffer writeBuffer = ByteBuffer.allocate(128);
writeBuffer.put("hello WebServer this is from WebClient".getBytes());
writeBuffer.flip();
socketChannel.write(writeBuffer);
//创建读数据缓存区对象
ByteBuffer readBuffer = ByteBuffer.allocate(128);
socketChannel.read(readBuffer);
//String 字符串常量,不可变;StringBuffer 字符串变量(线程安全),可变;StringBuilder 字符串变量(非线程安全),可变
StringBuilder stringBuffer=new StringBuilder();
//4.将Buffer从写模式变为可读模式
readBuffer.flip();
while (readBuffer.hasRemaining()) {
stringBuffer.append((char) readBuffer.get());
}
System.out.println("从服务端接收到的数据:"+stringBuffer);
socketChannel.close();
}
}

ServerSocketChannel 允许我们监听TCP链接请求,通过ServerSocketChannelImpl的 accept()方法 可以创建一个SocketChannel对象用户从客户端读/写数据。

服务端:

package socketchannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class WebServer {
public static void main(String args[]) throws IOException {
try {
//1.通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象,open方法的作用:打开套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//2.通过ServerSocketChannel绑定ip地址和port(端口号)
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 3333));
//通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
SocketChannel socketChannel = ssc.accept();
//3.创建写数据的缓存区对象
ByteBuffer writeBuffer = ByteBuffer.allocate(128);
writeBuffer.put("hello WebClient this is from WebServer".getBytes());
writeBuffer.flip();
socketChannel.write(writeBuffer);
//创建读数据的缓存区对象
ByteBuffer readBuffer = ByteBuffer.allocate(128);
//读取缓存区数据
socketChannel.read(readBuffer);
StringBuilder stringBuffer=new StringBuilder();
//4.将Buffer从写模式变为可读模式
readBuffer.flip();
while (readBuffer.hasRemaining()) {
stringBuffer.append((char) readBuffer.get());
}
System.out.println("从客户端接收到的数据:"+stringBuffer);
socketChannel.close();
ssc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行效果
客户端:
在这里插入图片描述
服务端:
在这里插入图片描述
通过上述实例代码,我们可以大概总结出SocketChannel和ServerSocketChannel的使用的一般使用规则:
考虑到篇幅问题,下面只给出大致步骤,不贴代码,可以结合上述实例理解。
客户端
1.通过SocketChannel连接到远程服务器
2.创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据
3.关闭SocketChannel
服务端
1.通过ServerSocketChannel 绑定ip地址和端口号
2.通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
3.创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据
4. 关闭SocketChannel和ServerSocketChannel

DatagramChannel的使用

DataGramChannel,类似于java 网络编程的DatagramSocket类;使用UDP进行网络传输, UDP是无连接,面向数据报文段的协议,对传输的数据不保证安全与完整 ;和上面介绍的SocketChannel和ServerSocketChannel的使用方法类似,所以这里就简单介绍一下如何使用。
1.获取DataGramChannel

//1.通过DatagramChannel的open()方法创建一个DatagramChannel对象
DatagramChannel datagramChannel = DatagramChannel.open();
//绑定一个port(端口)
datagramChannel.bind(new InetSocketAddress(1234));

上面代码表示程序可以在1234端口接收数据报。

2.接收/发送消息
接收消息:
先创建一个缓存区对象,然后通过receive方法接收消息,这个方法返回一个SocketAddress对象,表示发送消息方的地址:

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

发送消息:
由于UDP下,服务端和客户端通信并不需要建立连接,只需要知道对方地址即可发出消息,但是是否发送成功或者成功被接收到是没有保证的;发送消息通过send方法发出,改方法返回一个int值,表示成功发送的字节数:

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put("datagramchannel".getBytes());
buf.flip();
int send = channel.send(buffer, new InetSocketAddress("localhost",1234));

这个例子发送一串字符:“datagramchannel”到主机名为”localhost”服务器的端口1234上。

Scatter/Gather

Channel 提供了一种被称为 Scatter/Gather 的新功能,也称为本地矢量 I/O。Scatter/Gather 是指在多个缓冲区上实现一个简单的 I/O 操作。正确使用 Scatter / Gather可以明显提高性能。
大多数现代操作系统都支持本地矢量I/O(native vectored I/O)操作。当您在一个通道上请求一个Scatter/Gather操作时,该请求会被翻译为适当的本地调用来直接填充或抽取缓冲区,减少或避免了缓冲区拷贝和系统调用;
Scatter/Gather应该使用直接的ByteBuffers以从本地I/O获取最大性能优势。
Scatter/Gather功能是通道(Channel)提供的 并不是Buffer。

  • Scatter: 从一个Channel读取的信息分散到N个缓冲区中(Buufer).
  • Gather: 将N个Buffer里面内容按照顺序发送到一个Channel.

Scattering Reads
"scattering read"是把数据从单个Channel写入到多个buffer,如下图所示:
在这里插入图片描述
示例代码:

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

read()方法内部会负责把数据按顺序写进传入的buffer数组内。一个buffer写满后,接着写到下一个buffer中。
举个例子,假如通道中有200个字节数据,那么header会被写入128个字节数据,body会被写入72个字节数据;
注意:
无论是scatter还是gather操作,都是按照buffer在数组中的顺序来依次读取或写入的;
Gathering Writes
"gathering write"把多个buffer的数据写入到同一个channel中,下面是示意图
在这里插入图片描述
示例代码:

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

write()方法内部会负责把数据按顺序写入到channel中。
注意:
并不是所有数据都写入到通道,写入的数据要根据position和limit的值来判断,只有position和limit之间的数据才会被写入;
举个例子,假如以上header缓冲区中有128个字节数据,但此时position=0,limit=58;那么只有下标索引为0-57的数据才会被写入到通道中.

通道之间的数据传输

在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。

  • transferFrom(): transferFrom方法把数据从通道源传输到FileChannel
  • transferTo(): transferTo方法把FileChannel数据传输到另一个channel

 

版权声明
本文为[蜡笔小新v]所创,转载请带上原文链接,感谢
https://blog.51cto.com/u_9928699/2894377

  1. Hadoop面试题(一)
  2. Hadoop面试题总结-HDFS
  3. Hadoop面试题总结-HDFS
  4. Hadoop面试题总结(三)- MapReduce
  5. Hadoop面试题总结(三)- MapReduce
  6. Hadoop面试题(四)- YARN
  7. Hadoop面试题(四)- YARN
  8. Hadoop面试题总结(五)- 优化
  9. Hadoop面试题总结(五)- 优化
  10. 大数据面试题之Hadoop系列(深入部分)
  11. 大数据面试题之Hadoop系列(深入部分)
  12. Java NIO之拥抱Path和Files
  13. 【Java Web开发指南】云服务器部署项目供外网访问(Tomcat)
  14. 2020 年九大顶级 Java 框架!别再用一些落后的技术了!
  15. 【大数据哔哔集20210108】Spark Shuffle 和 Hadoop Shuffle有什么异同?
  16. 【大数据哔哔集20210108】Spark Shuffle 和 Hadoop Shuffle有什么异同?
  17. 不建议Java程序员用阿里巴巴规范,而使用GoogleGuava编程的原因
  18. 【大数据面试之对线面试官】MapReduce/HDFS/YARN面试题70连击
  19. 【大数据面试之对线面试官】MapReduce/HDFS/YARN面试题70连击
  20. Netty源码解析-概述篇
  21. Netty源码解析-概述篇
  22. Netty源码解析1-Buffer
  23. Netty源码解析1-Buffer
  24. Netty源码解析2-Reactor
  25. Netty源码解析2-Reactor
  26. Netty源码解析3-Pipeline
  27. Netty源码解析3-Pipeline
  28. Netty源码解析4-Handler综述
  29. Netty源码解析4-Handler综述
  30. Netty源码解析5-ChannelHandler
  31. Netty源码解析5-ChannelHandler
  32. Netty源码解析6-ChannelHandler实例之LoggingHandler
  33. Netty源码解析6-ChannelHandler实例之LoggingHandler
  34. Netty源码解析7-ChannelHandler实例之TimeoutHandler
  35. Netty源码解析7-ChannelHandler实例之TimeoutHandler
  36. Netty源码解析8-ChannelHandler实例之CodecHandler
  37. Netty源码解析8-ChannelHandler实例之CodecHandler
  38. Netty源码解析9-ChannelHandler实例之MessageToByteEncoder
  39. Netty源码解析9-ChannelHandler实例之MessageToByteEncoder
  40. 大数据面试题之Hbase系列
  41. 你可能需要的Kafka面试题与答案整理
  42. 你可能需要的Kafka面试题与答案整理
  43. 后起之秀Pulsar VS. 传统强者Kafka?谁更强
  44. 后起之秀Pulsar VS. 传统强者Kafka?谁更强
  45. 【大数据哔哔集20210123】别问,问就是Kafka最可靠
  46. 【大数据哔哔集20210123】别问,问就是Kafka最可靠
  47. 【大数据哔哔集20210124】有人问我Kafka Leader选举?我真没慌
  48. 【大数据哔哔集20210124】有人问我Kafka Leader选举?我真没慌
  49. 【大数据哔哔集20210117】Kafka 的高可靠性是怎么实现的
  50. 【大数据哔哔集20210117】Kafka 的高可靠性是怎么实现的
  51. Kafka Connect | 无缝结合Kafka构建高效ETL方案
  52. Kafka面试题总结(一)
  53. Kafka面试题总结(一)
  54. Kafka面试题整理(二)
  55. Kafka面试题整理(二)
  56. 基于Kafka Flink Redis的电商大屏实时计算案例
  57. 基于Kafka Flink Redis的电商大屏实时计算案例
  58. Google布隆过滤器与Redis布隆过滤器详解
  59. Google布隆过滤器与Redis布隆过滤器详解
  60. The spring boot process executes a function (four solutions)