一、流是什么
百度概念:
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
总结就是:流就是传输,传输的内容就是字节
在 Java 中我们常说的字节流、字符流其实本质就是对流传输内容的不同而划分的两种操作。字节流操作单位是字节,字符流操作单位是一个个字符。
前面说过,流是有起点和终点的。而又因为起点和终点的各不相同,流又可分为:输入流、输出流。
理解:
内存 -> 硬盘 = 输出流(OutputStream、Writer)
硬盘 -> 内存 = 输入流(InputStream、Reader)
对于流的操作 Java 提供了非常多的 API 操作,包位置:java.io、java.nio。
因为本篇不是教大家如何使用 API 的,所以其中的使用方法就不过多的介绍了,但我岂是那种不负责任的男人😀,已经帮你们找好要复习 IO 流操作的基础教程了👉👉👉点这里。
但为了引出 BIO、NIO及 AIO 相关概念,小小案例还是要写一下的,如下:
@Slf4j public class BioTest { /** * 流的形式操作文件 */ @Test public void streamTest() throws Exception { // 定义两个文件,in.txt 和 out.txt(提前在src目录下创建好两个文件) File inFile = new File("src/in.txt"); File outFile = new File("src/out.txt"); // 定义一个对 in.txt 操作的流对象 InputStream inputStream = new FileInputStream(inFile); // 开始读取文件 byte[] bytes = new byte[8]; inputStream.read(bytes); System.out.println(new String(bytes)); // 定义 out.txt 操作的流对象 OutputStream outputStream = new FileOutputStream(outFile); // 开始写出文件 outputStream.write(bytes); outputStream.write("\nout-write".getBytes(StandardCharsets.UTF_8)); } /** * 阻塞形式操作网络编程 */ @Test public void blockTest() throws Exception { /* 1、运行服务端发现,服务端在获取客户端连接及获取客户端数据时,都会堵塞 */ } @Test public void server() throws Exception { // 创建服务端对象 ServerSocket serverSocket = new ServerSocket(9528); log.info("创建了一个服务端对象,{}", serverSocket); // 获取客户端链接的 soclet 对象 Socket accept = serverSocket.accept(); log.info("获取到了客户端连接对象,{}", accept); InputStream inputStream = accept.getInputStream(); byte[] bytes = new byte[100]; inputStream.read(bytes); log.info("客户端发来的内容:{}", new String(bytes)); } @Test public void client() throws Exception { // 创建客户端,指定服务端ip及端口,进行连接 Socket client = new Socket("localhost", 9528); log.info("客户端创建完毕,{}", client); // 开始向务端发送消息 OutputStream outputStream = client.getOutputStream(); outputStream.write("hello world!".getBytes(StandardCharsets.UTF_8)); } }
上面案例展示了两种效果,一流操作、二阻塞操作。
而对于流和阻塞正是 Java 传统 IO(BIO) 的一种拙劣表现,流在数据的运输上效率比不上带有通道的缓冲区;而阻塞更比不上非阻塞的 Selector 操作。
二、BIO
同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
而且也是 Java 1.4 之前唯一的 IO 模式。
上面出现了两个名词:同步,阻塞,那么下面我先解释一下。
通过上面我写的代码案例和这张表格,相信大家对与同步及阻塞有了很清晰的认识了。下面我们一起看看 BIO 的模型图:
从图中可以看出,一个服务器会对应这多个客户端,每个客户端都对着不同的线程,这就导致了单线程环境下客户端 A 与服务端通信时,B、C 都需要进行等待阻塞,只有 A 通信完毕 B、C 客户端才能进行后续步骤。
案例代码,见第一小节。
三、NIO
同步非阻塞I/O模式,Java 1.4 之后开始支持,并提供了像 Channel , Selector,Buffer等抽象(后面会重点介绍这三大组件)。
我们都知道传统 BIO 是面向流的,而 NIO 意识到流的效率问题就提出了面向缓冲(块)的方式进行 IO 操作大大提升了传输效率。
而且 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO的非阻塞模式来开发。
NIO模型图如下:
案例:
@Slf4j public class NioTest { @Test public void serverTest() throws Exception{ //创建serverSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress(6666)); //设置为非阻塞 serverSocketChannel.configureBlocking(false); //得到Selector对象 try (Selector selector = Selector.open()) { //把ServerSocketChannel注册到selector,事件为OP_ACCEPT serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //如果返回的>0,表示已经获取到关注的事件 while (selector.select() > 0) { Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { //获得到一个事件 SelectionKey next = iterator.next(); //如果是OP_ACCEPT,表示有新的客户端连接 if (next.isAcceptable()) { //给该客户端生成一个SocketChannel SocketChannel accept = serverSocketChannel.accept(); accept.configureBlocking(false); //将当前的socketChannel注册到selector,关注事件为读事件,同时给socket Channel关联一个buffer accept.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); log.info("获取到一个客户端连接"); //如果是读事件 } else if (next.isReadable()) { //通过key 反向获取到对应的channel SocketChannel channel = (SocketChannel) next.channel(); //获取到该channel关联的buffer ByteBuffer buffer = (ByteBuffer) next.attachment(); while (channel.read(buffer) != -1) { buffer.flip(); log.info(new String(buffer.array(), 0, buffer.limit())); buffer.clear(); } } // 这个很重要,在处理完事件之后,要移除该事件 iterator.remove(); } } } } @Test public void clientTest() throws Exception{ //得到一个网络通道 SocketChannel socketChannel = SocketChannel.open(); //设置为非阻塞 socketChannel.configureBlocking(false); //提供服务器端的IP和端口 InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 9528); //连接服务器 if (!socketChannel.connect(inetSocketAddress)) { while (!socketChannel.finishConnect()) { log.info("连接需要时间,客户端不会阻塞...你可以去干别的事情了"); } } //连接成功,发送数据 String str = "J3-白起"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); socketChannel.close(); log.info("客户端退出"); } }
这段代码,可以体现服务端获取客户端连接时时不需要阻塞的,并且在读取客户端发来的数据时也是不需要阻塞有数据就读没有就往下执行,这也是 Netty 框架流行原因之一。
四、AIO
异步非阻塞I/O模式,在 Java 7 中引入了 NIO 的改进版 NIO 2。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
对于 AIO 在网上资料还不是很多,并且应用还不是很广泛,所以就…
五、最后
本篇主要是让大家对 Java IO 的模型体系有个大致了解,知道有 BIO、NIO、AIO 这一回事和了解同步、阻塞的区别就行。
对于具体的应用,我是没有具体展开说的,因为 BIO 是基础我已经贴过教程地址了☝☝☝。而 NIO 是学 Netty 的前提我后面会对这部分持续的输出,至于 AIO 应用还不是非常广泛我也不会,所以可以先不用关注了解就行。
好了,介绍了这篇,那下篇就是 NIO 讲解了,关注我,咱们下期见。