Fork me on GitHub

NIO源码测试分析

NIO即非阻塞式IO,它相比传统的IO更快且支持异步,由于它采用了通道和缓冲区的概念来读写文件,传统的以流的形式进行读写。
我们读文件需要先建立文件通道和缓冲区,而缓冲区有直接缓冲区和非直接缓冲区,缓冲区的建立不是通过new关键字,而是使用ByteBuffer类的静态方法。
非直接缓冲区:ByteBuffer.allocate(1024);返回一个类实例HeapByteBuffer,这个类的访问属性是包级私有的
直接缓冲区:ByteBuffer.allocateDirect(1024);返回一个类实例DirectByteBuffer,这个类的访问属性是包级私有的
通道的获取,通道有socket,file,这里分析了文件通道,我们获得通道需要使用java.io的FileInputStream和FileOutputStream的getChannel的方法,我们使用接口FileChannel存储通道实例对象的引用,即FileChannel fileChannel = fileOutputStream.getChannel();。其中getChannel方法返回的对象是FileChannelImpl类的实例,这个类是私有API sun包中的类。 下面是相关代码和注释说明

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOStudy {
    public static void readFile(){

        FileInputStream fileInputStream= null;
        try {
            fileInputStream = new FileInputStream("file.txt");//获得文件流
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
            FileChannel fileChannel=fileInputStream.getChannel();//获得通道 返回FileChannelImpl对象
            //创建缓冲区 底层分配具有指定大小的数组
            /*也可以使用自己创建的数组作为缓冲区中的数组
            byte array = new byte[1024];
            ByteBuffer byteBuffer = ByteBuffer.wrap(array);
            */
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);//返回非直接缓冲区 返回HeapByteBuffer对象

            //它将 limit 设置为与 capacity 相同。
            //它设置 position 为 0
            //将数据从通道读到缓冲区
        try {
            int length=-1;
            while ((length=fileChannel.read(byteBuffer))!=-1){
                byte[] bytes=byteBuffer.array();
                System.out.write(bytes,0,length);
                System.out.println();
                byteBuffer.clear();//将数据从通道读到缓冲区之前调用此方法重设缓冲区 使它可以接受读入的数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (fileChannel!=null){
                try {
                    fileChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileInputStream!=null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static void writeFile(){

        FileOutputStream fileOutputStream = null;
        try {
            //获得文件输出流
            fileOutputStream = new FileOutputStream("file.txt",true);//以拼接的形式写入数据到文件
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        //获得通道
        FileChannel fileChannel = fileOutputStream.getChannel();

        byte[] message={0x45,0x41,0x43};
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        for (int i=0;i<message.length;i++){
            //将数据一个一个的写入缓冲区
            //调用一次put方法position将会跟着改变
            byteBuffer.put(message[i]);
        }
        //将数据从缓冲区写入通道前调用此方法 让缓冲区可以将新读入的数据写入另一个通道
        //它将 limit 设置为当前 position。
        //它将 position 设置为 0
        byteBuffer.flip();
        try {
            //将数据从缓冲区写入通道
            fileChannel.write(byteBuffer);

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (fileOutputStream!=null){
                try {
                    fileChannel.close();
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }
    public static void main(String[] args) {
        //缓冲区的三个状态变量和访问方法
        //position limit capacity
        // position 变量跟踪已经写了多少数据获得读了多少数据
        //limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。position 总是小于或者等于 limit。
        //缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。
        //limit 决不能大于 capacity。
       //缓冲区分片slice 即子缓冲区 其和父缓冲区共享底层数组 只是通过position和limit来指定缓冲区的窗口后调用slice方法获得子缓冲区
        /*
        buffer.position(3);
        buffer.limit(7);
        ByteBuffer sonBuffer=buffer.slice();
         */
        /*
        将常规缓冲区转换为只读缓冲区 调用asReadOnlyBuffer()方法 应用场景:需要将缓冲区传递给某个对象但是不想让他修改
         */
        /*
         另一个有用的ByteBuffer是直接缓冲区,将以更快的方式执行IO
         以内存映射文件的方式创建直接缓冲区,它比基于流的和通道的IO更快
         */
        /*
        文件映射到内存
        MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,0, 1024 );
         */
        /*
        分散缓冲区和聚集缓冲区 ScatteringByteChannel 和 GatheringByteChannel
        这代表不是操作单个缓冲区而是操作缓冲区数组
        应用场景:网络传输中,将头信息放在一个缓存区而将正文信息放在另一个缓冲区
         */
         /*
         文件锁
         它并不是真的阻止访问而是劝告式的
         有排它锁和共享锁
         排它锁的获得
         RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" );
         FileChannel fc = raf.getChannel();
         FileLock lock = fc.lock( start, end, false );
         //执行某个文件操作后释放锁
         lock.release();
          */
         /*
         异步IO 使用非阻塞的方式读取和写入
         异步 I/O 的一个优势在于,它允许您同时根据大量的输入和输出执行 I/O。
         同步程序常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。
         使用异步 I/O,您可以监听任何数量的通道上的事件,不用轮询,也不用额外的线程。
         异步 I/O 中的核心对象名为 Selector。
         Selector 就是注册我们对各种 I/O 事件的兴趣的地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。
         Selector selector = Selector.open();//创建Selector对象
         我们将对不同的通道对象调用 register() 方法,
         以便注册我们对这些对象中发生的 I/O 事件的兴趣。register() 的第一个参数总是这个 Selector。
          */
         /*
         为了接收连接,我们需要一个 ServerSocketChannel。
         事实上,我们要监听的每一个端口都需要有一个 ServerSocketChannel 。
         对于每一个端口,我们打开一个 ServerSocketChannel,如下所示:
         ServerSocketChannel ssc = ServerSocketChannel.open();
         ssc.configureBlocking( false );//配置为非阻塞式

        ServerSocket ss = ssc.socket();
        InetSocketAddress address = new InetSocketAddress( ports[i] );
        ss.bind( address );
        下一步是将新打开的 ServerSocketChannels 注册到 Selector上。
        为此我们使用 ServerSocketChannel.register() 方法,如下所示:
        SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );
        SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知我们某个传入事件时,
        它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。
          */
         readFile();
         writeFile();

    }
}


最新评论

    还没有人评论...

当当

友情链接

Powered by Python. Copyright © 2017.

鄂ICP备17010875号. All rights reserved.