JAVA NIO 系列- 01概述
一. NIO 、BIO 介绍
-
BIO (blocking IO) , 即 阻塞型 IO,是JAVA 的传统 IO API , 是基于字节流 或字符流对数据进行操作而实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞卡在那里,它们之间的调用是可靠的线性顺序。优点就是代码比较简单直观, 缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
-
Java NIO(New IO / Non-blocking IO ) 就是 相对 BIO 提出的新的 JAVA IO API (New IO) ,是传统 IO API 的替代, 有时 NIO 也被称为非阻塞 IO .
例如,一个线程通知 通道(channel )将数据读入缓冲区(buffer)。当通道(channel )将数据读入缓冲区(buffer)时,线程可以做其他事情,而不用阻塞等待.一旦数据被读入缓冲区,线程就可以继续处理它。将数据写入通道也是如此。2.1 NIO 可以构建多路复用的、同步非阻塞 IO 程序等,提升IO的吞吐量以及性能,NIO 是面相缓冲区的,即 读数据的时候先将数据从通道读取到缓冲区,写数据的时候要将数据从缓冲区写如到通道
2.2 NIO 三大核心的概念:Channel(通道)、Buffer(缓冲区)、Selector(选择器).
IO | NIO |
---|---|
面向流(Stream oriented) | 面向缓冲区(Buffer oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non blocking IO) |
无 | 选择器(Selectors) |
二. 三大核心组件概念介绍
本小节先 对其概念进行一些解释,后续会有专门的章节来对每一个组件进行详细剖析.
上面我们说过, 标准 IO 是基于 字节流 和字符流, 而NIO 是基于 channel(通道) 和 buffers(缓存) 进行操作的, 数据总是从通道读取到缓冲区, 或者从缓冲区写入到通道 .
2.1 Channel
Java NIO 通道(Channel) 和 (Stream) 流 比较类似,但有一些区别:
- 可以对一个 Channel 进行读取 和写入, 但是 流(Streams) 通常是 单向的( 读 或者 写)
- Channel 可以异步的读写
- Channel 总是 读取到缓冲区 或者 从 缓冲区写入, Channel 必须对接的是 缓冲区(Buffer).
如上所述,将数据从通道读取到缓冲区,然后将数据从缓冲区写入通道。下面是一个例子:
下面是 Channel 和 Stream 的区别
区别 | Channel | Stream |
---|---|---|
是否支持异步 | 支持 | 不支持 |
是否支持双向传输 | 支持 ,支持,既可以从通道读取数据,也可以向通道写入数据 | 不支持 |
是否必须结合Buffer使用 | 必须结合Buffer使用 | 不 |
性能 | 较高 | 较低 |
2.2 Buffer
Java NIO 缓冲区(Buffer) 是用来缓存数据的, 是用来和 Channel 打交道的,如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块内存,您可以将数据写入其中,然后再读取数据。这个内存块被包装成 一个 NIO Buffer 对象,该对象提供了一系列方法 使得更容易的操作内存块。
2.3 Selector
Java NIO 选择器(Selector) 是NIO中的一个重要组件,可以监视一个或多个通道中的事件(如:连接打开、数据到达等) ,检查 通道 Channel 实例 状态, 并确定哪些 通道Channel 已经准备好进行读取或者 写入操作,通过这种方式, 单个线程可以管理多个通道Channels , 从而管理多个网络连接.
为什么使用Selector?
仅仅使用一个线程来处理多个通道 Channel 的优势就是 : 只需要较少的线程来 处理 通道 Channel, 事实上, 只需要一个线程就可以处理所有的通道 Channels。 对于操作系统来说 ,在线程之间切换还是比较耗性能的,并且每一个线程也会占用操作系统的一些资源(内存), 所以 使用线程越水越好.
但是请记住,现代操作系统和CPU在多任务处理方面越来越好,因此多线程的开销随着时间的推移会越来越小。事实上,如果一个CPU有多个内核,那么不执行多任务可能会浪费CPU资源。无论如何,这个设计讨论属于不同的文本。这里只需说明,您可以使用选择器(Selector),用一个线程处理多个通道。
以下是使用选择器处理3个通道的线程的图示:
三、 demo
3.1 BIO 例子
这里的流程主要是如下:
- Server 端先绑定一个端口号,一直阻塞到有client 端来
- client 端连上Server 端
- 双方开始进行I/O 通信
- client 结束, 然后 server 端 等待下一个Client 的到来.
Server 端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class IoServer {
public static void main(String[] args) throws IOException {
Socket socket = null;
InputStream inputStream = null;
OutputStream outputStream = null;
try {
//创建serverSocket
ServerSocket serverSocket = new ServerSocket(8888);
// 服务器会一直开启,等待client来连接
while (true) {
//监听等待客户端连接,这里会一直阻塞,直到到Client 连接
System.out.println("我在这里一直等待阻塞,直到有 Client 的连接");
socket = serverSocket.accept();
System.out.println("终于有一个Client 连接了,不然一直运行不到我这里啊");
// 获取Client端的数据
inputStream = socket.getInputStream();
// 回应Client端的数据
outputStream = socket.getOutputStream();
byte[] bytes = new byte[1024];
int readLen;
while (true) {
if ((readLen = inputStream.read(bytes)) != -1) {
String msg = new String(bytes, 0, readLen);
System.out.println(" client[Data] :" + msg);
//这里加了一个换行符,是由于用的 reader.readLine()读取的
outputStream.write((msg + "\n").getBytes());
outputStream.flush();
} else {
break;
}
}
System.out.println("Client 断开了,不然会一直阻塞在上面的 inputStream.read ");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (socket != null) {
socket.close();
}
}
}
}
client 端:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
public class IoClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
Scanner scanner = new Scanner( System.in);
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//获取输出字符流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
while (true) {
String msg = scanner.nextLine();
writer.write(msg);
writer.flush();
// 读
String str = reader.readLine();
System.out.println("server:" + str);
if ("BYE".equalsIgnoreCase(msg)) {
break;
}
}
socket.close();
}
}
3.2 NIO 例子
server端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NioServer {
public static void main(String[] args) throws IOException, InterruptedException {
//定义一个通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//设置为非阻塞方式
serverSocketChannel.configureBlocking(false);
//绑定端口号
serverSocketChannel.bind(new InetSocketAddress(9000));
//设置一个 缓冲区,channel 必须对接Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (true) {
// 这里不会阻塞,如果有连接上,就返回channel,下面的代码可以继续运行,没有就返回null
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
System.out.println("=Client端 连接了");
// 读取
int len;
try {
while ((len = socketChannel.read(byteBuffer)) > 0) {
//读取数据
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String msg = new String(bytes).trim();
System.out.println("Client:" + msg);
//返回给client
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (Exception e) {
}
} else {
System.out.println("还没有Client端 连接");
Thread.sleep(1000L);
}
}
}
}
client端:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class NioClient {
public static void main(String[] args) throws IOException, InterruptedException {
//定义一个通道
final SocketChannel channel = SocketChannel.open();
//设置非阻塞方式
channel.configureBlocking(false);
//设置服务端的ip和端口号
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9000);
//连接服务器端, 这里配置的是 非阻塞,所以会立刻返回,如果是false ,后续还需要调用finishConnect() 进一步判断.
if (!channel.connect(address)) {
//当连接服务端时,客户端不会处于阻塞状态,还需要继续执行下面的代码
while (!channel.finishConnect()) {
System.out.println("还在尝试连接中. . . ");
TimeUnit.MILLISECONDS.sleep(10);
}
}
//设置一个 缓冲区,写入 channel 必须对接Buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
//设置一个获取buffer
ByteBuffer readByteBuffer = ByteBuffer.allocate(1024);
// 发送数据 非阻塞模式下
while (true) {
//写给server
String msg = scanner.nextLine();
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
channel.write(byteBuffer);
byteBuffer.clear();
// 读取从server 传过来的数据
int len = -1;
while ((len = channel.read(readByteBuffer)) > 0) {
readByteBuffer.flip();
System.out.println("server:" + new String(readByteBuffer.array(), 0, len));
readByteBuffer.clear();
}
if ("bye".equalsIgnoreCase(msg)) {
break;
}
}
//关闭
channel.close();
}
}
这里的 serverSocketChannel.accept(); 没有阻塞,会继续运行下面的代码, 这里涉及 Buffer,Channel,等相关概念,在后续的章节详细描述, 这里仅给出一个例子.
四、 小结
本章主要是简单介绍了一下 NIO和IO 相关的概念, 并大体上解释了一下NIO的三大组件, 后续会对每一个组件详细介绍.
附:NIO 这块 内容主要是来着< JAVA NIO>书籍 和 JAVA-NIO 网站翻译
支付宝 | 微信 |
---|---|
如果有帮助记得打赏哦 | 特别需要您的打赏哦 |