历史回顾:

Java NIO 概览

Java NIO 之 Buffer(缓冲区)

其他高赞文章:

面试中关于Redis的问题看这篇就够了

一文轻松搞懂redis集群原理及搭建与使用

一 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是抽象类)。需要通过 InputStreamOutputStreamRandomAccessFile 获取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

参考:

官方JDK相关文档

谷歌搜索排名第一的Java NIO教程

《Java NIO》

《Java 8编程官方参考教程(第9版)》

欢迎关注我的微信公众号:”Java面试通关手册”(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):

Java NIO 之 Channel(通道)的更多相关文章

  1. Java NIO中的通道Channel(二)分散/聚集 Scatter/Gather

    什么是Scatter/Gather scatter/gather指的在多个缓冲区上实现一个简单的I/O操作,比如从通道中读取数据到多个缓冲区,或从多个缓冲区中写入数据到通道: scatter(分散): ...

  2. Java NIO中的通道Channel(一)通道基础

    什么是通道Channel 这个说实话挺难定义的,有点抽象,不过我们可以根据它的用途来理解: 通道主要用于传输数据,从缓冲区的一侧传到另一侧的实体(如文件.套接字...),反之亦然: 通道是访问IO服务 ...

  3. JAVA NIO之文件通道

    1.简介 通道是 Java NIO 的核心内容之一,在使用上,通道需和缓存类(ByteBuffer)配合完成读写等操作.与传统的流式 IO 中数据单向流动不同,通道中的数据可以双向流动.通道既可以读, ...

  4. 【Java nio】Channel

    package com.slp.nio; import org.junit.Test; import java.io.*; import java.nio.ByteBuffer; import jav ...

  5. java nio之channel

    一.通道(Channel):由 java.nio.channels 包定义的.Channel 表示 IO 源与目标打开的连接.Channel 类似于传统的“流”.只不过 Channel本身不能直接访问 ...

  6. JAVA NIO 之Channel

    缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.Channel 通道就是将数据传输给 ByteBuffer 对象或者从 ByteBuffer 对象获取数据进行传输. Channel 用于在 ...

  7. java输入输出 -- java NIO之文件通道

    一.简介 通道是 Java NIO 的核心内容之一,在使用上,通道需和缓存类(ByteBuffer)配合完成读写等操作.与传统的流式 IO 中数据单向流动不同,通道中的数据可以双向流动.通道既可以读, ...

  8. Java NIO教程 Channel

    Channel是一个连接到数据源的通道.程序不能直接用Channel中的数据,必须让Channel与BtyeBuffer交互数据,才能使用Buffer中的数据. 我们用FileChannel作为引子, ...

  9. 《JAVA NIO》Channel

    3.通道 Channle主要分为两类:File操作对应的FIleChannel和Stream操作对应的socket的3个channe. 1.这3个channel都是抽象类.其具体实现在SPI里面. 2 ...

随机推荐

  1. 使用userData兼容IE6-10,chrome,FF 及360等浏览器的本地存储

    开发过程中涉及本地存储的使用,IE很多版本都不支持localStorage,没办法,就得兼容使用userData了.废话不说了,看代码: (function(window){var LS;(funct ...

  2. hdu4285-circuits

    题意 一个 \(n\times m\) 的方格纸,有一些格子不能走.给出一个 \(k\) ,求有多少种方案,用 \(k\) 个不相交,不嵌套 的环覆盖所有可以走的格子.\(n,m\le 12\) . ...

  3. 基于c的简易计算器一

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <malloc.h&g ...

  4. 浅谈Tarjan算法及思想

    在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连 ...

  5. 解题:USACO13JAN Island Travels

    题面 好像没啥可说的,就当练码力了...... 先用BFS跑出岛屿,然后跑最短路求岛屿间的距离,最后状压DP得出答案 注意细节,码码码2333 #include<set> #include ...

  6. 一个最简单的使用Entity Framework 查询SQL 数据库的例子

    1.ADO.NET 3.5 Entity Framework是随着.net framework 3.5一起发布的,确认开发环境版本是大于等于3.5版本 2.确认已经安装了ADO.NET 3.5 Ent ...

  7. Docker入门与应用系列(三)容器管理

    一.启动容器 启动容器有两种方式,一种是基于镜像新建一个容器并启动,另一个是将终止状态的容器重新启动. 1.1 新建并启动 主要命令为 docker run 下面的命令输出一个”Hello,world ...

  8. KVM基本实现原理

    KVM 虚拟化技术概述 http://blog.csdn.net/yearn520/article/details/6461047 KVM 虚拟化技术在 AMD 平台上的实现 1.http://www ...

  9. 转:CocoaPods pod install/pod update更新慢的问题

    最近使用CocoaPods来添加第三方类库,无论是执行pod install还是pod update都卡在了Analyzing dependencies不动 原因在于当执行以上两个命令的时候会升级Co ...

  10. JavaApplet-Application Blocked..Your security setting have blocked an untrusted application from running..

    From:http://www.cnblogs.com/killerlegend/p/3892668.html Author:KillerLegend Date:2014.8.5 在看A*算法的时候, ...