Java NIO 是同步非阻塞的。NIO 相关的类放在 java.nio 包及子包下面,并且对原生的 IO 进行了很多类的改写。
NIO 是面向缓冲区或者是面向块编程的:数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中移动,这就增加了处理过程中的灵活性。
它有三大核心组件:
Channel:通道Buffer:缓冲区Selector:选择器
其核心组件结构如下图:
由上面的图可以看出:
- 一个 Channel 对应一个 Buffer一个 Selector 对应一个 Thread,但对应多个 ChannelSelector 会根据不同事件在各个通道上切换数据的读取/写入是通过 Buffer,这个跟 BIO 不同。BIO 是直接与通道打交道的NIO 中的 Buffer 是双向的(既可以读又可以写),但需要 flip() 方法切换;BIO 中不是双向流,要么是一个单独的输入流,要么就是一个输出流Channel 也是双向的
Client 不直接与 Channel 交互,而是通过中间媒介 Buffer 进行交互。
2. BufferBuffer 本质上是一个可以读、写数据的内存块,可以理解为一个容器对象。
Java 中的基本数据类型除了 boolean 类型外,其余的都有与之对应的 Buffer 类型。
下面以 IntBuffer 为例:
Buffer 使用示例:
public class BufferDemo { public static void main(String[] args) { // 1.创建 Buffer IntBuffer intBuffer = IntBuffer.allocate(5); for (int i = 0; i < intBuffer.capacity(); i++) { intBuffer.put(i); } // 2.Buffer 转换。写 --> 读 intBuffer.flip(); while (intBuffer.hasRemaining()) { System.out.println(intBuffer.get()); } } }
这里创建了一个 IntBuffer,大小为 5。然后,往其里面 put 了 5 个整数(Buffer 写)。由于 Buffer 既可以写又可以读。所以,在进行读取之前,进行 Buffer 切换 intBuffer.flip()
3. ChannelNIO 的通道类似于流,但有如下区别:
- 通道可以同时进行读、写,而流只能进行读或者写通道可以实现异步读、写数据通道可以从缓冲区读数据,也可以写数据到缓冲区
Channel 是一个接口:
public interface Channel extends Closeable { // ... }
常用的 Channel 类有:
FileChannle:用于文件的数据的读、写DatagramChannel:用于 UDP 的数据的读、写ServerSocketChannel:用于 TCP 的数据的读、写SocketChannel:用于 TCP 的数据的读、写
示例一:本地文件写数据
使用 ByteBuffer 和 FileChannel 将 “hello,JAVA” 写入到某个磁盘文件
public static void main(String[] args) throws Exception { String str = "hello, JAVA"; FileOutputStream out = new FileOutputStream("E:\temp.txt"); FileChannel fileChannel = out.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put(str.getBytes()); byteBuffer.flip(); fileChannel.write(byteBuffer); out.close(); }
运行上述代码后,便会在 E 盘中生成一个 temp.txt 文件,并且,其里面有内容。
示例二:本地文件读数据
使用 ByteBuffer 和 FileChannel 将 temp.txt 文件中的内容读取出来
public static void main(String[] args) throws Exception { File file = new File("E:\temp.txt"); FileInputStream in = new FileInputStream(file); FileChannel fileChannel = in.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length()); fileChannel.read(byteBuffer); // 将字节转化为字符串 String result = new String(byteBuffer.array()); System.out.println(result); in.close(); }
示例三:本地文件读、写数据
通过 FileChannel 和一个 Buffer 完成某个文件的拷贝
public class FileChannelRwDemo { public static void main(String[] args) throws Exception{ FileInputStream in = new FileInputStream("E:\temp.txt"); FileChannel inFileChannel = in.getChannel(); FileOutputStream out = new FileOutputStream("temp.txt"); FileChannel outFileChannel = out.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(512); while (true) { // 此行代码是重点 byteBuffer.clear(); int read = inFileChannel.read(byteBuffer); if (-1 == read) { break; } byteBuffer.flip(); outFileChannel.write(byteBuffer); } in.close(); out.close(); } }4. Selector
Selector 能够检测多个注册的通道上是否有事件发生。如果有事件发生,便获取事件,然后针对每个事件进行相应的处理。
只有在连接真正地有读、写事件发生时,才会进行读、写。这就大大地减少了系统的开销,并且,不必为每个连接都创建一个线程,不用去维护多个线程。
Selector 工作流程:
- 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel将 SocketChannel 注册到 Selector 上(SelectableChannel#register())注册后返回一个 SelectionKey,会和该 Selector 关联Selector 通过 select() 方法进行监听。该方法返回有事件发生的通道数进一步可得到 SelectionKeySelectionKey 通过 channel() 方法反向获取 SocketChannel,然后,可以进行 *** 作
示例:通过 NIO,进行服务端与客户端数据通讯
服务端:
public class NioServer { public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 绑定端口 serverSocketChannel.socket().bind(new InetSocketAddress(6666)); // 设置非阻塞 serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); // 将ServerSocketChannel注册到Selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { if (selector.select(1000) == 0) { System.out.println("服务器等待了1秒,无连接"); continue; } SetselectionKeys = selector.selectedKeys(); Iterator iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); // 如果有客户端连接 if (selectionKey.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("客户端连接成功,生成了一个 socketChannel :" + socketChannel.hashCode()); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (selectionKey.isReadable()) { SocketChannel socketChannel = (SocketChannel)selectionKey.channel(); // 获取与Channel关联的Buffer ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment(); socketChannel.read(byteBuffer); System.out.println("服务端收到了:" + new String(byteBuffer.array())); } iterator.remove(); } } } }
客户端:
public class NioClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666); // (正在)连接服务端 if (!socketChannel.connect(inetSocketAddress)) { while (!socketChannel.finishConnect()) { System.out.println("因为连接需要时间,客户端不用阻塞,可以做其它工作..."); } } // 连接成功 String str = "Hello Java"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); System.in.read(); } }
先运行服务端,再运行客户端,服务端打印出如下信息:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)