博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java NIO理解与使用
阅读量:4217 次
发布时间:2019-05-26

本文共 10488 字,大约阅读时间需要 34 分钟。

转:https://blog.csdn.net/qq_18860653/article/details/53406723

Netty的使用或许我们看着官网user guide还是很容易入门的。因为java nio使用非常的繁琐,netty对java nio进行了大量的封装。对于Netty的理解,我们首先需要了解NIO的原理和使用。所以,我也特别渴望去了解NIO这种通信模式。

官方的定义是:nio 是non-blocking的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。是不是很抽象?

在阅读这篇技术文档之后,收获了很多。包括对Java NIO的理解和使用,所以也特别的感谢作者。

首先,还是来回顾以下从这篇文档中学到的要点。

为什么要使用 NIO?

NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

NIO最重要的组成部分

通道 Channels

缓冲区 Buffers
选择器 Selectors

Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。

在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。

缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

Channel是一个对象,可以通过它读取和写入数据

看完下面这个例子,基本上就理解buffer和channel的作用了

 
  1. package yyf.java.nio.ibm;
  2. import java.io.*;
  3. import java.nio.*;
  4. import java.nio.channels.*;
  5. public
    class CopyFile {
  6. static public void main(String args[]) throws Exception {
  7. String infile =
    "c://test/nio_copy.txt";
  8. String outfile =
    "c://test/result.txt";
  9. FileInputStream fin =
    new FileInputStream(infile);
  10. FileOutputStream fout =
    new FileOutputStream(outfile);
  11. // 获取读的通道
  12. FileChannel fcin = fin.getChannel();
  13. // 获取写的通道
  14. FileChannel fcout = fout.getChannel();
  15. // 定义缓冲区,并指定大小
  16. ByteBuffer buffer = ByteBuffer.allocate(
    1024);
  17. while (
    true) {
  18. // 清空缓冲区
  19. buffer.clear();
  20. //从通道读取一个数据到缓冲区
  21. int r = fcin.read(buffer);
  22. //判断是否有从通道读到数据
  23. if (r == -
    1) {
  24. break;
  25. }
  26. //将buffer指针指向头部
  27. buffer.flip();
  28. //把缓冲区数据写入通道
  29. fcout.write(buffer);
  30. }
  31. }
  32. }

缓冲区主要是三个变量

position

limit
capacity
这三个变量一起可以跟踪缓冲区的状态和它所包含的数据。我们将在下面的小节中详细分析每一个变量,还要介绍它们如何适应典型的读/写(输入/输出)进程。在这个例子中,我们假定要将数据从一个输入通道拷贝到一个输出通道。
Position
您可以回想一下,缓冲区实际上就是美化了的数组。在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的 position 将会设置为3,指向数组中第四个元素。
同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。
Limit
limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
position 总是小于或者等于 limit。
Capacity
缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。
limit 决不能大于 capacity。

缓冲区作为一个数组,这三个变量就是其中数据的标记,也很好理解。

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

接下来来看看具体的使用把,我创建了一个直接收消息的服务器(一边接收一边写数据可能对于新手不好理解)

服务端:

 
  1. package yyf.java.nio.test;
  2. import java.net.InetSocketAddress;
  3. import java.net.ServerSocket;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.ServerSocketChannel;
  8. import java.nio.channels.SocketChannel;
  9. import java.util.Iterator;
  10. import java.util.Set;
  11. public
    class NioReceiver {
  12. @SuppressWarnings(
    "null")
  13. public static void main(String[] args) throws Exception {
  14. ByteBuffer echoBuffer = ByteBuffer.allocate(
    8);
  15. ServerSocketChannel ssc = ServerSocketChannel.open();
  16. Selector selector = Selector.open();
  17. ssc.configureBlocking(
    false);
  18. ServerSocket ss = ssc.socket();
  19. InetSocketAddress address =
    new InetSocketAddress(
    8080);
  20. ss.bind(address);
  21. SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
  22. System.out.println(
    "开始监听……");
  23. while (
    true) {
  24. int num = selector.select();
  25. Set selectedKeys = selector.selectedKeys();
  26. Iterator it = selectedKeys.iterator();
  27. while (it.hasNext()) {
  28. SelectionKey sKey = (SelectionKey) it.next();
  29. SocketChannel channel =
    null;
  30. if (sKey.isAcceptable()) {
  31. ServerSocketChannel sc = (ServerSocketChannel) key.channel();
  32. channel = sc.accept();
    // 接受连接请求
  33. channel.configureBlocking(
    false);
  34. channel.register(selector, SelectionKey.OP_READ);
  35. it.remove();
  36. }
    else
    if (sKey.isReadable()) {
  37. channel = (SocketChannel) sKey.channel();
  38. while (
    true) {
  39. echoBuffer.clear();
  40. int r = channel.read(echoBuffer);
  41. if (r <=
    0) {
  42. channel.close();
  43. System.out.println(
    "接收完毕,断开连接");
  44. break;
  45. }
  46. System.out.println(
    "##" + r +
    " " +
    new String(echoBuffer.array(),
    0, echoBuffer.position()));
  47. echoBuffer.flip();
  48. }
  49. it.remove();
  50. }
    else {
  51. channel.close();
  52. }
  53. }
  54. }
  55. }
  56. }

客户端(NIO):

 
  1. package yyf.java.nio.test;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.SelectionKey;
  5. import java.nio.channels.Selector;
  6. import java.nio.channels.SocketChannel;
  7. import java.util.Iterator;
  8. import java.util.Set;
  9. public
    class NioTest {
  10. public static void main(String[] args) throws Exception {
  11. ByteBuffer echoBuffer = ByteBuffer.allocate(
    1024);
  12. SocketChannel channel =
    null;
  13. Selector selector =
    null;
  14. channel = SocketChannel.open();
  15. channel.configureBlocking(
    false);
  16. // 请求连接
  17. channel.connect(
    new InetSocketAddress(
    "localhost",
    8080));
  18. selector = Selector.open();
  19. channel.register(selector, SelectionKey.OP_CONNECT);
  20. int num = selector.select();
  21. Set selectedKeys = selector.selectedKeys();
  22. Iterator it = selectedKeys.iterator();
  23. while (it.hasNext()) {
  24. SelectionKey key = (SelectionKey) it.next();
  25. it.remove();
  26. if (key.isConnectable()) {
  27. if (channel.isConnectionPending()) {
  28. if (channel.finishConnect()) {
  29. // 只有当连接成功后才能注册OP_READ事件
  30. key.interestOps(SelectionKey.OP_READ);
  31. echoBuffer.put(
    "123456789abcdefghijklmnopq".getBytes());
  32. echoBuffer.flip();
  33. System.out.println(
    "##" +
    new String(echoBuffer.array()));
  34. channel.write(echoBuffer);
  35. System.out.println(
    "写入完毕");
  36. }
    else {
  37. key.cancel();
  38. }
  39. }
  40. }
  41. }
  42. }
  43. }

运行结果:

 
  1. 开始监听……
  2. ##
    8
    12345678
  3. ##
    8
    9abcdefg
  4. ##
    8 hijklmno
  5. ##
    2 pq
  6. 接收完毕,断开连接


当然,BIO的客户端也可以,开启10个BIO客户端线程

 
  1. package yyf.java.nio.test;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.OutputStream;
  6. import java.net.InetAddress;
  7. import java.net.InetSocketAddress;
  8. import java.net.Socket;
  9. import java.net.UnknownHostException;
  10. import java.util.Random;
  11. import yyf.java.test.Main;
  12. public
    class BioClientTest {
  13. public static void main(String[] args) throws Exception {
  14. BioClient n =
    new BioClient();
  15. for (
    int i =
    0; i <
    10; i++) {
  16. Thread t1 =
    new Thread(n);
  17. t1.start();
  18. }
  19. }
  20. }
  21. class BioClient implements Runnable {
  22. @Override
  23. public void run() {
  24. try {
  25. Socket socket =
    new Socket(
    "127.0.0.1",
    8080);
  26. OutputStream os = socket.getOutputStream();
  27. InputStream is = socket.getInputStream();
  28. ByteArrayOutputStream bos =
    new ByteArrayOutputStream();
  29. String str = Thread.currentThread().getName() +
    "...........sadsadasJava";
  30. os.write(str.getBytes());
  31. StringBuffer sb =
    new StringBuffer();
  32. byte[] b =
    new
    byte[
    1024];
  33. int len;
  34. while ((len = is.read(b)) != -
    1) {
  35. bos.write(b,
    0, len);
  36. }
  37. is.close();
  38. os.close();
  39. socket.close();
  40. System.out.println(Thread.currentThread().getName() +
    " 写入完毕 " +
    new String(bos.toByteArray()));
  41. }
    catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
运行结果:

 
  1. ##
    8 Thread-
    4
  2. ##
    8 ........
  3. ##
    8 ...sadsa
  4. ##
    7 dasJava
  5. 接收完毕,断开连接
  6. ##
    8 Thread-
    3
  7. ##
    8 ........
  8. ##
    8 ...sadsa
  9. ##
    7 dasJava
  10. 接收完毕,断开连接
  11. ##
    8 Thread-
    9
  12. ##
    8 ........
  13. ##
    8 ...sadsa
  14. ##
    7 dasJava
  15. 接收完毕,断开连接
  16. ##
    8 Thread-
    7
  17. ##
    8 ........
  18. ##
    8 ...sadsa
  19. ##
    7 dasJava
  20. 接收完毕,断开连接
  21. ##
    8 Thread-
    0
  22. ##
    8 ........
  23. ##
    8 ...sadsa
  24. ##
    7 dasJava
  25. 接收完毕,断开连接
  26. ##
    8 Thread-
    5
  27. ##
    8 ........
  28. ##
    8 ...sadsa
  29. ##
    7 dasJava
  30. 接收完毕,断开连接
  31. ##
    8 Thread-
    2
  32. ##
    8 ........
  33. ##
    8 ...sadsa
  34. ##
    7 dasJava
  35. 接收完毕,断开连接
  36. ##
    8 Thread-
    8
  37. ##
    8 ........
  38. ##
    8 ...sadsa
  39. ##
    7 dasJava
  40. 接收完毕,断开连接
  41. ##
    8 Thread-
    1
  42. ##
    8 ........
  43. ##
    8 ...sadsa
  44. ##
    7 dasJava
  45. 接收完毕,断开连接
  46. ##
    8 Thread-
    6
  47. ##
    8 ........
  48. ##
    8 ...sadsa
  49. ##
    7 dasJava
  50. 接收完毕,断开连接

当然,这只是一个测试,对于一个服务器,是有读取,也有写出的,这是文档给的一个服务端例子

 
  1. package yyf.java.nio.ibm;
  2. import java.io.*;
  3. import java.net.*;
  4. import java.nio.*;
  5. import java.nio.channels.*;
  6. import java.util.*;
  7. public
    class MultiPortEcho {
  8. private
    int ports[];
  9. private ByteBuffer echoBuffer = ByteBuffer.allocate(
    5);
  10. public MultiPortEcho(int ports[]) throws IOException {
  11. this.ports = ports;
  12. go();
  13. }
  14. private void go() throws IOException {
  15. Selector selector = Selector.open();
  16. for (
    int i =
    0; i < ports.length; ++i) {
  17. ServerSocketChannel ssc = ServerSocketChannel.open();
  18. ssc.configureBlocking(
    false);
  19. ServerSocket ss = ssc.socket();
  20. InetSocketAddress address =
    new InetSocketAddress(ports[i]);
  21. ss.bind(address);
  22. SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
  23. System.out.println(
    "Going to listen on " + ports[i]);
  24. }
  25. while (
    true) {
  26. int num = selector.select();
  27. Set selectedKeys = selector.selectedKeys();
  28. Iterator it = selectedKeys.iterator();
  29. while (it.hasNext()) {
  30. SelectionKey key = (SelectionKey) it.next();
  31. if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
  32. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  33. SocketChannel sc = ssc.accept();
  34. sc.configureBlocking(
    false);
  35. SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
  36. it.remove();
  37. System.out.println(
    "Got connection from " + sc);
  38. }
    else
    if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
  39. SocketChannel sc = (SocketChannel) key.channel();
  40. int bytesEchoed =
    0;
  41. while (
    true) {
  42. echoBuffer.clear();
  43. int r = sc.read(echoBuffer);
  44. if (r <=
    0) {
  45. sc.close();
  46. break;
  47. }
  48. echoBuffer.flip();
  49. sc.write(echoBuffer);
  50. bytesEchoed += r;
  51. }
  52. System.out.println(
    "Echoed " + bytesEchoed +
    " from " + sc);
  53. it.remove();
  54. }
  55. }
  56. // System.out.println( "going to clear" );
  57. // selectedKeys.clear();
  58. // System.out.println( "cleared" );
  59. }
  60. }
  61. static public void main(String args[]) throws Exception {
  62. int ports[] =
    new
    int[] {
    8080 };
  63. for (
    int i =
    0; i < args.length; ++i) {
  64. ports[i] = Integer.parseInt(args[i]);
  65. }
  66. new MultiPortEcho(ports);
  67. }
  68. }

现在,我们就写个客户端去跟服务器通信,把发过去的返回来:

 
  1. package yyf.java.nio;
  2. import java.io.IOException;
  3. import java.net.InetSocketAddress;
  4. import java.net.SocketAddress;
  5. import java.nio.ByteBuffer;
  6. import java.nio.channels.SocketChannel;
  7. import javax.swing.ButtonGroup;
  8. public
    class NioClient {
  9. public static void main(String[] args) throws IOException {
  10. SocketChannel socketChannel = SocketChannel.open();
  11. SocketAddress socketAddress =
    new InetSocketAddress(
    "127.0.0.1",
    8080);
  12. socketChannel.connect(socketAddress);
  13. String str =
    "你好a";
  14. ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
  15. socketChannel.write(buffer);
  16. socketChannel.socket().shutdownOutput();
  17. buffer.clear();
  18. byte[] bytes;
  19. int count =
    0;
  20. while ((count = socketChannel.read(buffer)) >
    0) {
  21. buffer.flip();
  22. bytes =
    new
    byte[count];
  23. buffer.get(bytes);
  24. System.out.println(
    new String(buffer.array()));
  25. buffer.clear();
  26. }
  27. socketChannel.socket().shutdownInput();
  28. socketChannel.socket().close();
  29. socketChannel.close();
  30. }
  31. }


运行结果

server:

 
  1. Going to listen on
    8080
  2. Got connection from java.nio.channels.SocketChannel[connected local=/
    127.0.0.1:
    8080 remote=/
    127.0.0.1:
    63584]
  3. Echoed
    7 from java.nio.channels.SocketChannel[closed]

client:

你好a

对于NIO的入门就先到这里。
你可能感兴趣的文章
Java性能优化[0]:概述
查看>>
java Serializable和Externalizable序列化反序列化详解
查看>>
[java]垃圾收集器和堆
查看>>
JAVA 处理时间
查看>>
java(Web)中相对路径,绝对路径问题总结
查看>>
Java跨平台的原理
查看>>
Java线程之守护线程(Daemon)
查看>>
让网站性能最佳的34条黄金守则
查看>>
老紫竹JAVA基础培训(5),IF语句的使用
查看>>
JAVA生成EXCEL文件
查看>>
java读取xml配置文件(小结)
查看>>
Java新手的通病[4]:异常处理使用不当
查看>>
java编程中的常见异常
查看>>
java读写文件大全
查看>>
Java垃圾回收器的工作机制
查看>>
SQL优化34条 java面试题
查看>>
java sql常见面试题
查看>>
Java同步、异步相关知识点
查看>>
java线程总结
查看>>
Java性能优化[1]:基本类型 vs 引用类型
查看>>