Java NIO 之 Channel(通道)
历史回顾:
其他高赞文章:
一 Channel(通道)介绍
通常来说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
参考:
欢迎关注我的微信公众号:”Java面试通关手册”(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):
Java NIO 之 Channel(通道)的更多相关文章
- Java NIO中的通道Channel(二)分散/聚集 Scatter/Gather
什么是Scatter/Gather scatter/gather指的在多个缓冲区上实现一个简单的I/O操作,比如从通道中读取数据到多个缓冲区,或从多个缓冲区中写入数据到通道: scatter(分散): ...
- Java NIO中的通道Channel(一)通道基础
什么是通道Channel 这个说实话挺难定义的,有点抽象,不过我们可以根据它的用途来理解: 通道主要用于传输数据,从缓冲区的一侧传到另一侧的实体(如文件.套接字...),反之亦然: 通道是访问IO服务 ...
- JAVA NIO之文件通道
1.简介 通道是 Java NIO 的核心内容之一,在使用上,通道需和缓存类(ByteBuffer)配合完成读写等操作.与传统的流式 IO 中数据单向流动不同,通道中的数据可以双向流动.通道既可以读, ...
- 【Java nio】Channel
package com.slp.nio; import org.junit.Test; import java.io.*; import java.nio.ByteBuffer; import jav ...
- java nio之channel
一.通道(Channel):由 java.nio.channels 包定义的.Channel 表示 IO 源与目标打开的连接.Channel 类似于传统的“流”.只不过 Channel本身不能直接访问 ...
- JAVA NIO 之Channel
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.Channel 通道就是将数据传输给 ByteBuffer 对象或者从 ByteBuffer 对象获取数据进行传输. Channel 用于在 ...
- java输入输出 -- java NIO之文件通道
一.简介 通道是 Java NIO 的核心内容之一,在使用上,通道需和缓存类(ByteBuffer)配合完成读写等操作.与传统的流式 IO 中数据单向流动不同,通道中的数据可以双向流动.通道既可以读, ...
- Java NIO教程 Channel
Channel是一个连接到数据源的通道.程序不能直接用Channel中的数据,必须让Channel与BtyeBuffer交互数据,才能使用Buffer中的数据. 我们用FileChannel作为引子, ...
- 《JAVA NIO》Channel
3.通道 Channle主要分为两类:File操作对应的FIleChannel和Stream操作对应的socket的3个channe. 1.这3个channel都是抽象类.其具体实现在SPI里面. 2 ...
随机推荐
- js & auto copy
js & auto copy https://developer.mozilla.org/zh-CN/docs/Web/Events/copy Ctrl + C Command + C doc ...
- P2461 [SDOI2008]递归数列
题目描述 一个由自然数组成的数列按下式定义: 对于i <= k:ai = bi 对于i > k: ai = c1ai-1 + c2ai-2 + ... + ckai-k 其中bj 和 cj ...
- C++ STL 常用排序算法
C++ STL 常用排序算法 merge() 以下是排序和通用算法:提供元素排序策略 merge: 合并两个有序序列,存放到另一个序列. 例如: vecIntA,vecIntB,vecIntC是用ve ...
- MySql数据库迁移图文展示
MySql数据库的数据从一台服务器迁移到另外一台服务器需要将数据库导出,再从另外一台服务器导入.方法有很多,MySql配套的相关工具都有这个功能.phpMyAdmin就可以做,但是这个加载起来慢,推荐 ...
- BZOJ2276 [Poi2011]Temperature 【单调队列】
题目链接 BZOJ2276 题解 一开始看错题,以为求的是可以不连续的,想出一个奇怪的线段树,发现空间根本开不下?? 题目要我们求连续的最长可能不下降区间 对于区间\([l,r]\)如果合法,当且仅当 ...
- tokenizer
http://blog.csdn.net/beyond__devil/article/details/52829241
- git pull 总要求输入账号和密码解决?
如果你用git从远程pull拉取代码,每次都要输入密码,那么执行下面命令即可 git config --global credential.helper store 这个命令则是在你的本地生成一个账号 ...
- Zabbix利用msmtp+mutt发送邮件报警
操作系统:CentOS 7 Web环境:Nginx+MySQL+PHP zabbix版本:zabbix-2.4.8.tar.gz 邮件服务:msmtp-1.4.32.tar.bz2 #http ...
- DHCP 服务器功能
DHCP服务器不仅可以分配IP地址,同时也可以分配网关和DNS服务器地址
- 编译geth报错的解决方法 make: *** [geth] 错误 1
在centos下安装了go1.9.1版本,编译go-ethereum时报错: [root@localhost go-ethereum]# make gethbuild/env.sh go run bu ...