zoukankan      html  css  js  c++  java
  • Java NIO 与 IO之间的区别

    概述

    Java NIO提供了与标准IO不同的IO工作方式: 
    • Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
    • Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
    • Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
     

    使用场景

    NIO

    • 优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;
    • 如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;

    传统的IO

    • 适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的;
    • 如果需要管理同时打开不太多的连接,这些连接会发送大量的数据;
     

    NIO vs IO区别

    NIO vs IO之间的理念上面的区别(NIO将阻塞交给了后台线程执行)
    • IO是面向流的,NIO是面向缓冲区的
      • Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;
      • NIO则能前后移动流中的数据,因为是面向缓冲区的
    • IO流是阻塞的,NIO流是不阻塞的
      • Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
      • Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 
      • 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
    • 选择器
      • Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。 
     
     
     

    Java NIO 由以下几个核心部分组成: 


    • Channels
    • Buffers
    • Selectors
     
    基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示: 
     

    Channel

     

    Channel的实现: (涵盖了UDP 和 TCP 网络IO,以及文件IO)

    • FileChannel
    • DatagramChannel
    • SocketChannel
    • ServerSocketChannel

    读数据:

    • int bytesRead = inChannel.read(buf);

    写数据:

    • int bytesWritten = inChannel.write(buf);  
    还有部分的使用,如配置Channel为阻塞或者非阻塞模式,以及如何注册到Selector上面去,参考Selector部分;
     

    Buffer

    Buffer实现: (byte,  char、short, int, long, float, double )

    • ByteBuffer
    • CharBuffer
    • DoubleBuffer
    • FloatBuffer
    • IntBuffer
    • LongBuffer
    • ShortBuffer

    Buffer使用

    读数据
    • flip()方法
      • 将Buffer从写模式切换到读模式
      • 调用flip()方法会将position设回0,并将limit设置成之前position的值。
      • buf.flip();
    •  (char) buf.get()
      • 读取数据
    • Buffer.rewind()Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用
      • 将position设回0,所以你可以重读Buffer中的所有数据
      • limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
    • Buffer.reset()方法,恢复到Buffer.mark()标记时的position
    • 一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
    • clear()方法会:
      • 清空整个缓冲区。
      • position将被设回0,limit被设置成 capacity的值
    • compact()方法:
      • 只会清除已经读过的数据;任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
      • 将position设到最后一个未读元素正后面,limit被设置成 capacity的值
    写数据
    • buf.put(127);  
     

    Buffer的三个属性

    • capacity:含义与模式无关;Buffer的一个固定的大小值;Buffer满了需要将其清空才能再写;
      • ByteBuffer.allocate(48);该buffer的capacity为48byte
      • CharBuffer.allocate(1024);该buffer的capacity为1024个char 
    • position:含义取决于Buffer处在读模式还是写模式(初始值为0,写或者读操作的当前位置)
      • 写数据时,初始的position值为0;其值最大可为capacity-1
      • 将Buffer从写模式切换到读模式,position会被重置为0
    • limit:含义取决于Buffer处在读模式还是写模式(写limit=capacity;读limit等于最多可以读取到的数据)
      • 写模式下,limit等于Buffer的capacity
      • 切换Buffer到读模式时, limit表示你最多能读到多少数据;
     

    Selector

    概述

        Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
        要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。 

    使用

    1. 创建:Selector selector = Selector.open();  
    2. 注册通道:
      • channel.configureBlocking(false);  
        • //与Selector一起使用时,Channel必须处于非阻塞模式
        • //这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式(而套接字通道都可以)
      • SelectionKey key = channel.register(selector, Selectionkey.OP_READ); 
        • //第二个参数表明Selector监听Channel时对什么事件感兴趣
        • //SelectionKey.OP_CONNECT  SelectionKey.OP_ACCEPT  SelectionKey.OP_READ SelectionKey.OP_WRITE
        • //可以用或操作符将多个兴趣组合一起
      • SelectionKey
        • 包含了interest集合 、ready集合 、Channel 、Selector 、附加的对象(可选)
        • int interestSet = key.interestOps();可以进行类似interestSet & SelectionKey.OP_CONNECT的判断
    3. 使用:
      • select():阻塞到至少有一个通道在你注册的事件上就绪了
      • selectNow():不会阻塞,不管什么通道就绪都立刻返回
      • selectedKeys():访问“已选择键集(selected key set)”中的就绪通道
      • close():使用完selector需要用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效
     
    1. Set selectedKeys = selector.selectedKeys();  
    2. Iterator keyIterator = selectedKeys.iterator();  
    3. while(keyIterator.hasNext()) {  
    4.     SelectionKey key = keyIterator.next();  
    5.     if(key.isAcceptable()) {  
    6.         // a connection was accepted by a ServerSocketChannel.  
    7.     } else if (key.isConnectable()) {  
    8.         // a connection was established with a remote server.  
    9.     } else if (key.isReadable()) {  
    10.         // a channel is ready for reading  
    11.     } else if (key.isWritable()) {  
    12.         // a channel is ready for writing  
    13.     } 
    14.     keyIterator.remove();//注意这里必须手动remove;表明该selectkey我已经处理过了;
    15. }
     
     

    Java测试关键代码

    1. RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");  
    2. FileChannel inChannel = aFile.getChannel();  //从一个InputStream outputstream中获取channel
    3.   
    4. //create buffer with capacity of 48 bytes  
    5. ByteBuffer buf = ByteBuffer.allocate(48);  
    6.   
    7. int bytesRead = inChannel.read(buf); //read into buffer.  
    8. while (bytesRead != -1) {  
    9.   
    10.   buf.flip();  //make buffer ready for read  
    11.   
    12.   while(buf.hasRemaining()){  
    13.       System.out.print((char) buf.get()); // read 1 byte at a time  
    14.   }  
    15.   
    16.   buf.clear(); //make buffer ready for writing  
    17.   bytesRead = inChannel.read(buf);  
    18. }  
    19. aFile.close();  

     

    文件通道

    1. RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");  
    2. FileChannel inChannel = aFile.getChannel();  
    读数据
    1. ByteBuffer buf = ByteBuffer.allocate(48);  
    2. int bytesRead = inChannel.read(buf);  
    写数据
    1. String newData = "New String to write to file..." + System.currentTimeMillis();   
    2. ByteBuffer buf = ByteBuffer.allocate(48);  
    3. buf.clear();  
    4. buf.put(newData.getBytes());  
    5. buf.flip();  
    6. while(buf.hasRemaining()) {  
    7.     channel.write(buf);  

    Socket 通道

    1. SocketChannel socketChannel = SocketChannel.open();  
    2. socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));  
    读数据
    1. ByteBuffer buf = ByteBuffer.allocate(48);  
    2. int bytesRead = socketChannel.read(buf);  
    写数据
    1. String newData = "New String to write to file..." + System.currentTimeMillis();  
    2. ByteBuffer buf = ByteBuffer.allocate(48);  
    3. buf.clear();  
    4. buf.put(newData.getBytes());  
    5. buf.flip();  
    6. while(buf.hasRemaining()) {  
    7.     socketChannel.write(buf);  
    8. }  

    ServerSocket 通道

    1. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
    2. serverSocketChannel.socket().bind(new InetSocketAddress(9999));  
    3. while(true){  
    4.     SocketChannel socketChannel =  
    5.             serverSocketChannel.accept();  
    6.     //do something with socketChannel...  
     

    Datagram 通道(channel的读写操作与前面的有差异)

    1. DatagramChannel channel = DatagramChannel.open();  
    2. channel.socket().bind(new InetSocketAddress(9999));  
    读数据
    1. ByteBuffer buf = ByteBuffer.allocate(48);  
    2. buf.clear();  
    3. channel.receive(buf);
    4. //receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。 
    写数据
      1. String newData = "New String to write to file..." + System.currentTimeMillis();  
      2. ByteBuffer buf = ByteBuffer.allocate(48);  
      3. buf.clear();  
      4. buf.put(newData.getBytes());  
      5. buf.flip();  
      6. int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));  
  • 相关阅读:
    pscp 从win10远程传输文件到centos7,多个虚拟机之间传文件
    Spring Aop中四个重要概念,切点,切面,连接点,通知
    查看Java的汇编指令
    Spring集成GuavaCache实现本地缓存
    RocketMq消息 demo
    使用axis1.4生成webservice的客户端代码
    oracle ORA-00060死锁查询、表空间扩容
    mysql 1449 : The user specified as a definer ('usertest'@'%') does not exist 解决方法 (grant 授予权限)
    软件安装
    动态雪花飘落
  • 原文地址:https://www.cnblogs.com/cxxjohnson/p/9079505.html
Copyright © 2011-2022 走看看