zoukankan      html  css  js  c++  java
  • Java NIO学习

    Java NIO(转自:http://www.iteye.com/magazines/132-Java-NIO#585)

    Java NIO提供了与标准IO不同的IO工作方式:

      • Channels,Buffers
      • Asynchronous IO
      • Selectors

    Buffer的基本用法

    使用Buffer读写数据一般遵循以下四个步骤:

      • 分配内存:ByteBuffer buf = ByteBuffer.allocate(48); 

      • 写入数据到Buffer:int bytesRead = inChannel.read(buf); //从Channel写到Buffer

      • 调用flip()方法:flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

      • 从Buffer中读取数据:int bytesWritten = inChannel.write(buf);  //从Buffer读取数据到Channel  byte aByte = buf.get();//使用get()方法从Buffer中读取数据

      • 调用clear()方法或者compact()方法:一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。

    当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

    一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

    Buffer的三个属性:

    • capacity:固定的大小值

    • position:当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

    • limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读

           到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

    scatter,gather:channel从buffer中读取,写入数据

    ByteBuffer header = ByteBuffer.allocate(128);  
    ByteBuffer body   = ByteBuffer.allocate(1024);  
    ByteBuffer[] bufferArray = { header, body };  
    channel.write(bufferArray);//写入到channel
    channel.read(bufferArray);//写入到buffer

    通道之间的数据传输   :SocketChannel会一直传输数据直到目标buffer被填满。

    transFrom()

    transTo()   

    RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); 
    FileChannel      fromChannel = fromFile.getChannel();  
    RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");  
    FileChannel      toChannel = toFile.getChannel();  
    long position = 0;  
    long count = fromChannel.size();  
    toChannel.transferFrom(position, count, fromChannel); 
    fromChannel.transferTo(position, count, toChannel); 

    Selector(选择器)

    selector是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写设备准备好的组建,这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

    创建selector:Selector selector = Selector.open();

    向selector注册通道:

    channel.configureBlocking(false);//与Selector使用时,Channel必须处于非阻塞模式下,套接字通道可以,FileChannel不可以切换
    SelectionKey key = channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

    SelectionKey:注册时,register()方法会返回一个SelectionKey,它包含一些你感兴趣的属性:

            interest集合:是你所选择的感兴趣的事件集合,可以监听四种不同类型的事件(Connect,Accept,Read,Write)

            ready集合:是通道已经准备就绪的操作集合,可通过四个方法检测channel中什么事件或操纵已经就绪

              • selectionKey.isAcceptable();  
              • selectionKey.isConnectable();  
              • selectionKey.isReadable();  
              • selectionKey.isWritable(); 

            Channel:

                    Channel channel = selectionKey.channel();

            Selector:

                    Selector selector = selectionKey.selector();

            附加对象(可选):selectionKey.attach(theObject);     Object attachedObj = selectionKey.attachment();  

    通过Selector选择通道

    select()方法:

    • select()阻塞到至少有一个通道在你注册的事件上就绪了。 返回的int值表示有多少通道已经就绪了。
    • select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
    • selectNow()不会阻塞,不管什么通道就绪都立刻返回(译者注:此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。)。 

    selectedKeys():调用select()方法后,根据得到有多少个通道就绪了,然后可以通过selector的selectedKeys()方法,访问已选择键集中的就绪通道

    Set selectedKeys = selector.selectedKeys();  

    当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。
    可以遍历这个已选择的键集合来访问就绪的通道。打开一个Selector,注册一个通道注册到这个Selector上(通道的初始化过程略去),然后持续监控这个Selector的四种事件(接受,连接,读,写)是否就绪。如下:

    1. Selector selector = Selector.open();  
    2. channel.configureBlocking(false);  
    3. SelectionKey key = channel.register(selector, SelectionKey.OP_READ);  
    4. while(true) {  
    5.   int readyChannels = selector.select();  
    6.   if(readyChannels == 0) continue;  
    7.   Set selectedKeys = selector.selectedKeys();  
    8.   Iterator keyIterator = selectedKeys.iterator();  
    9.   while(keyIterator.hasNext()) {  
    10.     SelectionKey key = keyIterator.next();  
    11.     if(key.isAcceptable()) {  
    12.         // a connection was accepted by a ServerSocketChannel.  
    13.     } else if (key.isConnectable()) {  
    14.         // a connection was established with a remote server.  
    15.     } else if (key.isReadable()) {  
    16.         // a channel is ready for reading  
    17.     } else if (key.isWritable()) {  
    18.         // a channel is ready for writing  
    19.     }  
    20.     keyIterator.<tuihighlight class="tuihighlight"><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-decoration:underline;" onclick="return false;">remove</a></tuihighlight>();  
    21.   }  
    22.  

    SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。

    文件通道

    FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

    打开channel:需要通过使用一个InputStream,OutputStream或RandomAccessFile来获取一个FileChannel实例

    RandomAccessFile aFile = new RandomAccessFile("xx.txt, "rw");
    FileChannel inChannel = aFile.getChannel();

    从FileChannel中读取数据

    ByteBuffer buf = ByteBuffer.allocate(48);
    int byteRead = inChannel.read(buf); //表示有多少分字节被读到Buffer中

    向FileChannel写数据

    String newData = "new String to write to file ..." + System.currentTimeMills();
    long pos = channel.position(); //FileChannel的当前位置
    channel.position(pos + 123); //设置FileChannel的当前位置 ByteBuffer buf
    = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()){ channel.write(buf); }

    关闭FileChannel

    channel.close();

     截取文件,文件将指定长度后面的部分被删除

    channel.truncate(1024);

     强制写入磁盘:FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。

    要保证这一点,需要调用force()方法。force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。 下面的例子同时将文件数据和元数据强制写到磁盘上:

    channel.force(true);

     

  • 相关阅读:
    解决IE8不兼容通过class名获取元素的方法
    移动端页面遇到过的各种坑
    强大的正则表达式
    弹性盒子布局
    vue环境搭建
    fullpage.js使用指南
    ES5原生api(1)
    双色球中奖率分析(python)
    使用python脚本的3D引擎Panda3d
    Python lambda介绍
  • 原文地址:https://www.cnblogs.com/hangzhi/p/9117918.html
Copyright © 2011-2022 走看看