当前位置: 移动技术网 > IT编程>开发语言>Java > Nio学习

Nio学习

2018年10月17日  | 移动技术网IT编程  | 我要评论

nio学习

文章是自己学习后的一个总结,如果有什么理解不对的地方,欢迎留言

这一章你只需要明白什么是nio,nio中有什么,nio能做什么即可。

更为详细的解释,可以去看java nio这本书,当然博主也在慢慢学习,也会在别的随笔中写出更为详细的解释!加油啊小伙伴!

 什么是nio?

  java.nio全称java non-blocking io(实际上是 new io),是指jdk1.4 及以上版本里提供的新api(new io) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

 

什么是阻塞式什么是非阻塞式?

  • 传统io: 传统的io是阻塞式的,当服务器要从一个文件系统读取数据的时候,需要建立一个线程去读取,但是刚开始读取的时候,有可能文件系统并没有把数据准备好,但是该线程只能等待文件系统把数据准备好再进行读的操作,无法先去做别的事情。这就是阻塞
  • nio : nio可以解决传统io中的堵塞问题,使用了selector (选择器,稍后会详解)监听,数据有没有准备好,当数据准备好,服务器在为该读操作分配线程。这样一来,服务器可以很好的利用仅有的线程资源。

就好比生活中在某滴上预约了车,估摸这8:00司机会到达,你7:50就在小区门口等待,那么你将有10分钟的等待时间,这就是阻塞。相反,你等8:00司机到了并且给你打电话通知你已经到小区门口了,这个时候你再出门。这样你是不是就节约了10分钟,而这10分钟内,你可以选择干点有人生意义的事情,这样就是非阻塞。

但是请注意一点,并不是用了nio就不会发生堵塞!!!并不是用了nio就不会发生堵塞!!!并不是用了nio就不会发生堵塞!!!重要的事情说三遍。在nio中也分阻塞和非阻塞,后面会说。

 

你应该了解的nio中三个组件

nio中有三个重要概念,(⊙o⊙)…怎么说捏,如果没记住,那你恐怕没有办法继续向下看。

  • buffer: 缓冲区,用来存储数据的容器。实际上是个数组
  • channel: 通道, 表示io源和应用程序之间的连接
  • selector: 选择器,如果channel注册进选择器中,那么selector就可以监听channel。一个selector可以监听多个channel.

buffer可以理解成火车,channel可以理解为铁路,buffer在channel中行驶。因此,我能得出一个结论,channel并存储数据!!!

 channel每次都从buffer中读数据,也把数据写入到buffer中

buffer学习()

buffer实际上是一个数组,buffer可以有多种类型, java中的基本类型都可以和buffer关联(boolean除外).

突然间觉得boolean好可怜,人家不带它玩,用的最多的可能就是bytebuffer了。

在buffer中还有三个重要概念:容量、限制和位置.

  • position:标记当前操作数所在位置
  • limit:表示缓冲区中可以操作数据的大小,limit后的数据不能读写
  • capacity: 标记当前容量大小

 

比如我初始化一个

bytebuffer buffer = new bytebuffer.allocate(5);

(allocate可以指定buffer缓冲区的大小。)那么position,limit,capacith的关系如下

 

画图太难了,臣妾做不到啊!!!这个时候我

string str = "123";
buffer.put(str.getbytes());

那么position就会移动到第三格,limit,capacity还是不会变。

但是如果如果把buffer切换成读模式

buffer.flip()

 那么当前position: 3 , limit: 3, capacity: 5

    @test
    public void test() {
        string str = "123";
        //指定buffer的容量大小
        bytebuffer buffer = bytebuffer.allocate(1024);
        //属性
        system.out.println("-----------------------");
        //当前操作的数据所在的位置
        system.out.println(buffer.position());
        //界限,表示缓冲区中可以操作数据的大小, limit后的数据不能读写
        system.out.println(buffer.limit());
        //缓冲区中最大存储数据容量
        system.out.println(buffer.capacity());
        
        buffer.put(str.getbytes());
        system.out.println("---------put--------------");
        system.out.println(buffer.position());
        system.out.println(buffer.limit());
        system.out.println(buffer.capacity());
        //切换成读模式
        buffer.flip();
        system.out.println("---------flip--------------");
        system.out.println(buffer.position());
        system.out.println(buffer.limit());
        system.out.println(buffer.capacity());
        
        bytearrs = new byte[buffer.limit()];
        bytebuffer bytebuffer = buffer.get(bytearrs);
        system.out.println(bytebuffer.tostring());
        // rewind,切换成读模式,可以重新读
        buffer.rewind();
        system.out.println("===========rewind============");
        system.out.println(buffer.tostring());
        
        //清空缓存区
        buffer.clear();
    }
view code

channel()

 

channel中主要的实现类:filechannel,socketchannel,serversocketchannel。

获取channel的方法:

jdk1.7以前: 通过io流获得到channel

    /**
     * 利用通道完成文件复制
     * @throws ioexception 
     */
    @test
    public void test() {
        long start = system.currenttimemillis();
        fileinputstream fis = null;
        fileoutputstream fos = null;
        filechannel inchannel = null;
        filechannel outchannel = null;
        try {
            //jdk1.7以前nio 的获取通道的写法
            fis  = new fileinputstream("./resource/java nio.pdf");
            fos = new fileoutputstream("./resource/democopytest.jpeg");
            
            //1.获取通道
            inchannel = fis.getchannel();
            outchannel = fos.getchannel();
            //2.创建缓冲区,并分配大小
            bytebuffer bytebuffer = bytebuffer.allocate(1024);
            //3.把数据写进缓冲区
            while (inchannel.read(bytebuffer) != -1) {
                
                //4.切换读取数据模式
                bytebuffer.flip();
                outchannel.write(bytebuffer);
                bytebuffer.clear();
            }
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        } finally {
            try {
                if (outchannel != null) {
                    outchannel.close();
                }
                if (inchannel != null) {
                    inchannel.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (fis != null) {
                    fis.close();
                }
            } catch (ioexception e) {
                // todo auto-generated catch block
                e.printstacktrace();
            }
        }
        long end = system.currenttimemillis();
        system.out.println("耗费时间非直接缓存" + (end - start));
    }
jdk1.7以前

 jdk1.7以后:可以直接通过open方法获得到channel

    @test
    public void test2() {
        long start = system.currenttimemillis();
        filechannel inchannel = null;
        filechannel outchannel = null;
        
        try {
            //建立通道
            inchannel = filechannel.open(paths.get("./resource/java nio.pdf"), standardopenoption.read);
            outchannel = filechannel.open(paths.get("./resource/java niocopytest2.pdf"), standardopenoption.read, standardopenoption.write, standardopenoption.create);
            
        //    inchannel.transferto(0, inchannel.size(), outchannel);
            outchannel.transferfrom(inchannel, 0, inchannel.size());
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        } finally {
            try {
                if (outchannel != null) {
                    outchannel.close();
                }
                if (inchannel != null) {
                    inchannel.close();
                }
            } catch (ioexception e) {
                // todo auto-generated catch block
                e.printstacktrace();
            }

        }
        long end = system.currenttimemillis();
        system.out.println("耗费时间直接缓冲区" + (end - start));
    }
filechannel.open
    /**
     * 直接缓冲区,用内存映射文件完成
     * 可能遇到的问题: 文件已经copy完成,但是程序可能没有完成。我们只能控制什么时候写入映射文件,但是不能控制什么时候从映射文件写入磁盘
     */
    @test
    public void test1() {
        long start = system.currenttimemillis();
        filechannel inchannel = null;
        filechannel outchannel = null;
        mappedbytebuffer inmap = null;
        mappedbytebuffer outmap = null;
        
        try {
            //建立通道
            inchannel = filechannel.open(paths.get("./resource/java nio.pdf"), standardopenoption.read);
            outchannel = filechannel.open(paths.get("./resource/java niocopytest2.pdf"), standardopenoption.read, standardopenoption.write, standardopenoption.create);
            //因为是内存文件映射,我们不需要读流,内存映射文件
            //mappedbytebuffer 相当于allocatedriect()
            inmap = inchannel.map(mapmode.read_only, 0, inchannel.size());
            outmap = outchannel.map(mapmode.read_write, 0, inchannel.size());
            byte[] bytes = new byte[inmap.limit()];
            inmap.get(bytes);
            outmap.put(bytes);
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        } finally {
            try {
                if (outchannel != null) {
                    outchannel.close();
                }
                if (inchannel != null) {
                    inchannel.close();
                }
            } catch (ioexception e) {
                // todo auto-generated catch block
                e.printstacktrace();
            }

        }
        long end = system.currenttimemillis();
        system.out.println("耗费时间直接缓冲区" + (end - start));
    }
view code

selector( )

使用selector可以实现非阻塞,创建selector

selector selector = selector.open();

 

阻塞式nio和非阻塞式nio

nio是如何实现非阻塞式io的?

嗯。。这个问题我们还是得看一张图。阻塞式是这样的,客户端直接和服务器端建立连接,不需要中间监听器

没用selector所以还是阻塞式nio

    @test
    public void servertest() {
        serversocketchannel serversocketchannel = null;
        filechannel filechannel = null;
        socketchannel socketchannel = null;
        try {
            serversocketchannel = serversocketchannel.open();
            serversocketchannel.bind(new inetsocketaddress(9898));
            socketchannel = serversocketchannel.accept();
            bytebuffer bytebuffer = bytebuffer.allocate(1024);
            filechannel = filechannel.open(paths.get("./resource/blocktest.jpeg"), standardopenoption.write, standardopenoption.create);

            while (socketchannel.read(bytebuffer) != -1) {
                bytebuffer.flip();
                system.out.println(bytebuffer);
                filechannel.write(bytebuffer);
                bytebuffer.clear();
            }
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        } finally {
            try {
                if (filechannel != null) {
                    filechannel.close();
                }
                if (serversocketchannel != null) {
                    serversocketchannel.close();
                }
                if (socketchannel != null) {
                    socketchannel.close();
                }
            } catch (ioexception e) {
                // todo auto-generated catch block
                e.printstacktrace();
            }
        }
    }
view code

非阻塞式是这样的 

 

 

  当客户端发请求时会先到达selector,selector就像一堵墙一样堵在了客户端和服务器段。发请求的同时把channel注册到selector中。selector监听channel的状态

渠道分为4种状态:

public static final int op_read = 1 << 0; 
public static final int op_write = 1 << 2; 
public static final int op_connect = 1 << 3; 
public static final int op_accept = 1 << 4;

拿socketchannel来举例子,服务器端

    @test
    public void server() {
        serversocketchannel serversocketchannel = null;
        try {
            serversocketchannel = serversocketchannel.open();
            serversocketchannel.configureblocking(false);
            //创建selector对象
            selector selector = selector.open();
            //把serversocketchannel交给selector管理,并绑定监听状态op_accept
            serversocketchannel.register(selector, selectionkey.op_accept);
            serversocketchannel.bind(new inetsocketaddress(9898));
            while (selector.select() > 0) {
                //获得迭代器
                iterator<selectionkey> iterator = selector.selectedkeys().iterator();
                while (iterator.hasnext()) {
                    selectionkey selectionkey = iterator.next();
                    //判断channel是否是 is ready to accept准备
                    if (selectionkey.isacceptable()) {
                        //服务器连接请求!!!这个时候才连接,而不是像阻塞io那样,不管三七二十一直接连接请求
                        socketchannel socketchannel = serversocketchannel.accept();
                        //设置成非阻塞
                        socketchannel.configureblocking(false);
                        //注册channel到selector,这里注意,socketchannel是一个新的渠道也需要注册
                        //监听read
                        socketchannel.register(selector, selectionkey.op_read);
                    } else if (selectionkey.isreadable()){
                        //获取当前选择器中读就绪的channel
                        socketchannel socketchannel = (socketchannel)selectionkey.channel();
                        bytebuffer buffer = bytebuffer.allocate(1024);
                        int len = 0;
                        while ((len = socketchannel.read(buffer) )!= -1) {
                            buffer.flip();
                            //这里可以把客户端传过来的byte做一些转化
                            system.out.println(buffer);
                            buffer.clear();
                        }
                    }
                    //从迭代器中把已经完成是事件移除
                    iterator.remove();
                }
            }
        } catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
        }
    }

demo github 地址 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网