都用程序来说明,当然远不止下面的程序的对比。还需要自己取更深的理解。以下的内容的大前提。
大前提:所有的IO操作都分为磁盘IO和网络IO。都是以磁盘IO为例。
应用场景copy网上一句话:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
1, 同步阻塞的BIO
程序举例:
package com.auto.demo;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class NIOTest {
public static void main(String[] args) throws FileNotFoundException {
// 输入输出文件流
FileInputStream fis = new FileInputStream("C:\\Users\\zy962\\Desktop\\tip说明.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\zy962\\Desktop\\NIO测试.txt");
//ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 创建缓冲区(注意这个缓冲区和NIO的区别)
byte[] buffer = new byte[1024];
int length = 0;
try {
while ((length = fis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}
//byte[] data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2, 同步非阻塞的NIO(JDK1.4)
程序举例:
package com.auto.demo;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOTest {
public static void main(String[] args) throws FileNotFoundException {
// 输入输出文件流
FileInputStream fis = new FileInputStream("C:\\Users\\zy962\\Desktop\\tip说明.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\zy962\\Desktop\\NIO测试.txt");
// 输入输出管道
FileChannel fci = fis.getChannel();
FileChannel fco = fos.getChannel();
// 创建缓冲
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 写入
int flag = 0;
try {
while ((flag = fci.read(buffer)) != -1) {
buffer.flip();
fco.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fco.close();
fci.close();
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
读完程序我们可以知道,最大的差距:操作过程间加入了管道和缓冲区
操作过程:输入输出流获取-> 创建输入输出管道->创建缓冲区->通过输入管道写入缓冲区->通过输出管道缓冲区内容写入。注意在输出写入钱需要filp重置,写入完成后需要clear重置。
3, 异步非阻塞的AIO( 即是NIO 2.0)
能力有限,以后理解了再来写。
借用别人的话。
AIO与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。
NIO(含AIO)家族截图:
网络IO的理解:(这个还需要更加加深理解)
首先、socket的目的是创建连接,发送消息,socket是对tcp/ip或udp/ip协议的封装,socket本身只是一个调用接口,他是一个中间工具存在于操作系统的内核中,你可以理解为api,有两端,客户端需要ip+port去连接发送消息,服务端serversocket需要指定端口接受处理消息以及回复消息。
对BIO理解:
BioServer
package zhouyi.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class BioServer {
public static void main(String[] args) {
// 指定服务端口号
int port = 8002;
ServerSocket server = null;
try {
server = new ServerSocket(port);
Socket socket = null;
// 是否有数据传输
while (true) {
// 只能接受一个服务
socket = server.accept();
// 新的请求就需要新线程去处理
new Thread(new BioServerHandler(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (server != null) {
try {
server.close();
server = null;
System.out.println("服务关闭结束");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
处理线程:
package zhouyi.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class BioServerHandler implements Runnable {
private Socket socket;
public BioServerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 客服端信息读入
BufferedReader reader = null;
// 返回客服端信息
PrintWriter writer = null;
try {
reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
writer = new PrintWriter(this.socket.getOutputStream(), true);
StringBuffer buffer = new StringBuffer();
String bodyString = null;
while (true) {
bodyString = reader.readLine();
if (bodyString == null) {
break;
}
buffer.append(";" + bodyString + ";");
System.out.println("请求消息主题:" + bodyString);
writer.println(buffer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
writer = null;
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (this.socket != null) {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
this.socket = null;
}
}
}
}
客服端:
package zhouyi.bio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class BioClientServer {
public static void main(String[] args) {
int port = 8002;
Socket socket = null;
BufferedReader reader = null;
PrintWriter writer = null;
try {
// IP和端口号绑定
socket = new Socket("127.0.0.1", port);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
writer = new PrintWriter(socket.getOutputStream(), true);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
writer.println("客服端请求request");
System.out.println("客服端请求已经发出");
try {
// 返回结果如下
String readString = reader.readLine();
System.out.println("返回结果如下:" + readString);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
writer = null;
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
reader = null;
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
socket = null;
}
}
}
}
运行结果:(我们可以看到这是一对一的,来一个请求就需要新建一个线程,当请求很多时候,显然是低效的,而且服务器要占用很多)
NIO的理解:
服务端
package zhouyi.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NioServer {
// 管道管理器
private Selector selector;
/**
* 绑定端口
* @param port
*/
public void initServer(int port) {
// 获得一个socket通道
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置为非阻塞通道
serverSocketChannel.configureBlocking(false);
// 将通道绑定到对应的serversocket的port上
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 上面做完进程中就有socke去监听设置的端口,这里只是进入了监听状态,并不是和客户端的连接状态,因此后面还需要再处理。
// 获得一个通道管理,多路复用
this.selector = selector.open();
// 将通道管理器和该通道绑定,并为该通道注册SelectKey.OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 采用轮询的方式监听selector上是否需要处理的事件,如果有,则进行处理
*/
public void listen() {
System.out.println("服务端启动成功!");
// 轮询访问selector
while (true) {
try {
selector.select();
// 处理selector中的注册事件
Iterator<?> it = this.selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
// 删除已选key,防止重复
it.remove();
serverHandler(key);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 请求处理
* @param key
*/
private void serverHandler(SelectionKey key) {
// 这就是客户端连接后的处理。
// 客服端请求连接事件(轮询)
if (key.isAcceptable()) {
serverHandlerAccept(key);
// 获得了可读事件
} else if (key.isReadable()) {
serverHandlerRead(key);
}
}
/**
* 处理读事件
* @param key
*/
private void serverHandlerRead(SelectionKey key) {
// 得到读通道
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
int read = channel.read(buffer);
if (read > 0) {
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println(msg);
// 写数据
ByteBuffer outBuffer = ByteBuffer.wrap(("服务器收到的消息" + msg).getBytes());
channel.write(outBuffer);
} else {
System.out.println("服务器关闭");
key.cancel();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 处理连接请求
* @param key
*/
private void serverHandlerAccept(SelectionKey key) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
//获取和客服端连接的通道
try {
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
System.out.println("新的客服端连接");
socketChannel.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 启动服务端测试
* @param args
*/
public static void main(String[] args) {
NioServer server = new NioServer();
server.initServer(8082);
server.listen();
}
}
客服端:
package zhouyi.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class NioClient {
public static Selector selector;
public static SocketChannel socketClientChannel;
private static void init() {
try {
selector = Selector.open();
socketClientChannel = SocketChannel.open();
// 设置为阻塞模式
socketClientChannel.configureBlocking(false);
socketClientChannel.register(selector, SelectionKey.OP_READ);
/*
* while (!socketClientChannel.finishConnect()) {
*
* }
*/
System.out.println("已经连接服务器!");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
init();
SocketChannel socketChannel = NioClient.socketClientChannel;
ByteBuffer buffer = ByteBuffer.allocate(1024);
new ExecutorThread(selector, socketChannel).start();
while (true) {
// 输入
Scanner scanner = new Scanner(System.in);
String word = scanner.nextLine();
buffer.put(word.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
}
static class ExecutorThread extends Thread {
private Selector selector;
public ExecutorThread(Selector selector, SocketChannel socketChannel) {
this.selector = selector;
}
@Override
public void run() {
// 轮询等待服务器给返回值
while (true) {
try {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
if (selectionKey.isValid()) {
// 可读的那么就来读一波
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey
.channel();
// 读取服务器的返回值
socketChannel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes));
buffer.clear();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
总感觉网络IO理解起来很费劲!思维有点有点绕,或许自己都是本地模拟的场景,显得总有点不合实际的感觉。这种思维网络IO
的理解我还需要更加更加深刻的理解。
真的要去浅显的理解的话,也还好。
其实它和磁盘IO差距只有一个,那就是需要建立连接,其他的操作很是类似。