欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Nio学习

程序员文章站 2022-03-26 08:42:56
Nio学习 文章是自己学习后的一个总结,如果有什么理解不对的地方,欢迎留言 这一章你只需要明白什么是NIO,NIO中有什么,NIO能做什么即可。 更为详细的解释,可以去看JAVA NIO这本书,当然博主也在慢慢学习,也会在别的随笔中写出更为详细的解释!加油啊小伙伴! 什么是Nio? java.nio ......

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学习(java.nio.buffer)

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

Nio学习

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

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

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

 

比如我初始化一个

bytebuffer buffer = new bytebuffer.allocate(5);

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

Nio学习

 

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

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

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

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

buffer.flip()

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

Nio学习
    @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(java.nio.channels.channel)

Nio学习

 

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

获取channel的方法:

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

Nio学习
    /**
     * 利用通道完成文件复制
     * @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

Nio学习
    @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
Nio学习
    /**
     * 直接缓冲区,用内存映射文件完成
     * 可能遇到的问题: 文件已经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(java.nio.channels.selector )

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

selector selector = selector.open();

 

阻塞式nio和非阻塞式nio

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

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

Nio学习

没用selector所以还是阻塞式nio

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

非阻塞式是这样的 

Nio学习

 

 

  当客户端发请求时会先到达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 地址