io流获取返回值 ruby_java高级之IO流

点击蓝字关注我们

BIO、NIO、AIO区别


  • BIO:Block IO 同步阻塞式 IO,在传统的java.io包下,它基于流模型实现(面向的IO操作),提供了我们最熟知的一些IO功能,例如File 抽象、输入输出流等。BIO的交互方式是同步、阻塞的方式,即在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。

  • NIO:non-blocking IO 同步非阻塞 IO,是在JDK1.4中引入的NIO框架(java.nio包),可以看作是传统IO的升级,NIO支持面向缓冲区的、基于通道的IO操作。NIO提供了Selector、 Channel、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞IO程序,提供了更接近操作系统底层的高性能数据操作方式。

  • AIO:Asynchronous IO 是NIO的升级,在JDK1.7中实现,也叫 NIO2,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制。

NIO知识整理


  • NIO主要有三大核心部分:Selector(选择器)、Channel(通道)、Buffer(缓冲区);

  • NIO是面向缓冲区,面向块的编程,数据读取到一个稍后会处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用NIO可以提供非阻塞式的高伸缩性网络。

NIO与IO区别


40c3eb6695323a57b425a56552805e41.png

  • IO是面向流的,流是单向的,比如从文件(磁盘、网络)到程序的过程中使用的输入输出流都是单向的。

  • NIO是面向缓冲区的,NIO在文件(磁盘、网络)和程序之间建立通道(Channel),传输的数据通过缓冲区进行存取,缓冲区在通道中进行传递运输,例如火车与铁轨的关系,是双向的。

通道与缓冲区


  • 通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

  • Channel 负责传输、连接, Buffer 负责数据存储、操作。

  • 缓冲区(Buffer):一个用于特定基本数据类型的容器。它主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

缓冲区Buffer


  • 由java.nio包定义,所有缓冲区都是Buffer抽象类的子类。常用子类如下:

7550f32bc0da16f624280a88c657c7b7.png

  • Buffer中的四个核心属性:

    • 标记、位置、限制、容量遵守以下不变式:0 <= mark <= position <= limit <= capacity。

public abstract class Buffer {    //标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position。    private int mark = -1;    //下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制    private int position = 0;    //限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。    private int limit;    //容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。    private int capacity;}

67722c248d25192bb75623cf8f227a71.png

  • Buffer 的常用方法:

ad3d13f7969ad79ebe9bdfb97544ffad.png

  • Buffer的子类常用方法,注意字节缓冲区中的直接缓冲区非直接缓冲区的区别。

    • 直接缓冲区可以通过allocateDirect()和FileChannel的map()方法来创建,返回值为MappedByteBuffer。

efd61b2344423a860ef32e98fed3ea73.png

  • 代码练习

@Testpublic void testBufer(){    String str = "Practice Buffer";    //1、创建一个字节缓冲区 分配大小为128    ByteBuffer byteBuffer = ByteBuffer.allocate(128);    //2、核心属性  0 <= mark <= position <= limit <= capacity    //  2.1拿到容量    System.out.println("容量为:"+byteBuffer.capacity());    //  2.2拿到限制    System.out.println("限制为:"+byteBuffer.limit());    //  2.3拿到位置    System.out.println("当前位置为:"+byteBuffer.position());    //3、put() 将数据写入缓冲区    byteBuffer.put(str.getBytes());    //4、flip() 切换读写模式    byteBuffer.flip();    //5、get()  读取数据    byte[] bytes = new byte[byteBuffer.limit()];    byteBuffer.get(bytes);    System.out.println(new String(bytes,0,bytes.length));    //6、rewind() 可重复读  将位置设为为 0    byteBuffer.rewind();    //7、读两个位置的数据    byte[] dst = new byte[byteBuffer.limit()];    byteBuffer.get(dst, 0, 2);    System.out.println(new String(dst, 0, 2));    //拿到位置和限制    System.out.println("当前位置为:"+byteBuffer.position()+",当前限制为"+byteBuffer.limit());    //8、核心属性标记  mark    byteBuffer.mark();    //9、在读两个字节的数据    byteBuffer.get(dst,2,2);    //拿到位置和限制    System.out.println("当前位置为:"+byteBuffer.position()+",当前限制为"+byteBuffer.limit());    //10、reset() 恢复到 标记mark位置    byteBuffer.reset();    //拿到位置和限制    System.out.println("当前位置为:"+byteBuffer.position()+",当前限制为"+byteBuffer.limit());    //11、hasRemaining() 判断缓冲区是否还有元素    if(byteBuffer.hasRemaining()){        //12、可以操作的数量  返回 position 和 limit 之间的元素个数        System.out.println(byteBuffer.remaining());    }    //取消设置的 mark    byteBuffer.rewind();    //13、clear(); 清空缓冲区  但是缓冲区中数据仍然存在    byteBuffer.clear();    System.out.println((char)byteBuffer.get());    //14、分配直接缓冲区    ByteBuffer buf = ByteBuffer.allocateDirect(1024);    //判断字节缓冲区是直接还是非直接    System.out.println(buf.isDirect());}

通道(Channel)


  • 在java.nio.channels包中定义,它表示 IO 源与目标打开的连接。可以将其类比于传统的“流”。但Channel本身不能直接访问数据, 它只能与Buffer进行交互。

  • 获取通道:

    • 可以对支持通道的对象调用getChannel()方法。支持通道的类有:本地IO为FileInputStream、FileOutputStream、RandomAccessFile,网络IO为DatagramSocket、Socket、 ServerSocket;

    • 在NIO2中,通过通道的静态方法 open() 打开并返回指定通道;

    • 在NIO2中,使用Files类的静态方法newByteChannel()获取字节通道。

/** * 1、FileChannel 的open()方法  作用打开或创建文件,返回文件通道以访问该文件。 * 2、参数:path - 打开或创建文件的路径   options - 指定文件打开方式的选项 * 3、OpenOption 使用StandardOpenOption枚举类指定 *      APPEND:如果文件打开 WRITE访问,则字节将被写入文件的末尾而不是开头。 *      CREATE:创建一个新文件(如果不存在)。 *      CREATE_NEW:创建一个新的文件,如果该文件已经存在失败。 *      DELETE_ON_CLOSE:关闭时删除。 *      DSYNC:要求将文件内容的每次更新都与底层存储设备同步写入。 *      READ:打开阅读权限。 *      SPARSE:稀疏文件 *      SYNC:要求将文件内容或元数据的每次更新都同步写入底层存储设备。 *      TRUNCATE_EXISTING:如果文件已经存在,并且打开 WRITE访问,则其长度将截断为0。 *      WRITE:打开以进行写入。 */public static FileChannel open(Path path, OpenOption... options) throws IOException{    Set<OpenOption> set = new HashSet<OpenOption>(options.length);    Collections.addAll(set, options);    return open(path, set, NO_ATTRIBUTES);}
  • 可以利用通道完成整个数据传输,不使用缓冲区,使用通道的transferFrom()和transferTo()方法。

  • 通道的分散(Scatter)和聚集(Gather):

    • 分散读取(Scattering Reads)是指从Channel中读取的数据“分散”到多个 Buffer中。

    • 聚集写入(Gathering Writes)是指将多个Buffer中的数据“聚集”到 Channel。

  • 通道的常用方法:

    a28a98ff9231e19d81d537e610da5db4.png

  • 代码练习

//为了简化 未做异常处理@Testpublic void testChannel() throws IOException {    //一、使用非直接缓冲区完成文件复制    //1、创建文件输入流    FileInputStream fileInputStream = new FileInputStream("爱情与友情.jpg");    //2、创建文件输出流    FileOutputStream fileOutputStream = new FileOutputStream("爱情与友情6.jpg");    //3、fileInputStream 与 fileOutputStream 支持通道 获取通道    FileChannel inputStreamChannel = fileInputStream.getChannel();    FileChannel outputStreamChannel = fileOutputStream.getChannel();    //4、分配缓冲区    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);    //5、将通道中数据存入缓冲区    while (inputStreamChannel.read(byteBuffer)!=-1){        //6、切换读写模式        byteBuffer.flip();        //7、将数据写入到通道中        outputStreamChannel.write(byteBuffer);        //8、清空缓冲区        byteBuffer.clear();    }//*********************************************************************************//    //二、使用直接缓冲区完成文件复制 通道的静态方法 open() 打开并返回指定通道    //1、创建通道    FileChannel inChannle = FileChannel.open(Paths.get("爱情与友情.jpg"), StandardOpenOption.READ);    FileChannel outChannle = FileChannel.open(Paths.get("爱情与友情7.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);    //2、直接缓冲区,内存映射文件    // 此频道文件的区域直接映射到内存中。只读:READ_ONLY  读写:READ_WRITE  私有:PRIVATE     MappedByteBuffer inMappedByteBuffer = inChannle.map(FileChannel.MapMode.READ_ONLY, 0, inChannle.size());    MappedByteBuffer outMappedByteBuffer = outChannle.map(FileChannel.MapMode.READ_WRITE, 0, inChannle.size());    byte[] bytes = new byte[inMappedByteBuffer.limit()];    inMappedByteBuffer.get(bytes);    outMappedByteBuffer.put(bytes);    inChannle.close();    outChannle.close();       //*********************************************************************************//    //三、通道之间的数据传输    FileChannel inChannle1 = FileChannel.open(Paths.get("爱情与友情.jpg"), StandardOpenOption.READ);    FileChannel outChannle1 = FileChannel.open(Paths.get("爱情与友情8.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);    //inChannle1.transferTo(0,inChannle.size(),outChannle1);    outChannle1.transferFrom(inChannle1,0,inChannle1.size());    inChannle1.close();    outChannle1.close();//*********************************************************************************//    //四、分散和聚集    //四-1 分散读取    //1、创建一个随机存取文件流    RandomAccessFile randomAccessFile = new RandomAccessFile("hello1.txt", "rw");    //2、获取通道    FileChannel channel = randomAccessFile.getChannel();    //3、获取缓冲区    ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);    ByteBuffer byteBuffer2 = ByteBuffer.allocate(100);    //4、分散读取    ByteBuffer[] byteBuffers = {byteBuffer1,byteBuffer2};    //5、将通道中数据分散到buffer中    channel.read(byteBuffers);    for (ByteBuffer byteBuffer3 : byteBuffers) {        //6、读写切换        byteBuffer3.flip();    }    //7、查看结果    System.out.println(new String(byteBuffers[0].array(), 0, byteBuffers[0].limit()));    System.out.println("===================================");    System.out.println(new String(byteBuffers[1].array(), 0, byteBuffers[1].limit()));    //四-2 聚集写入    RandomAccessFile raf2 = new RandomAccessFile("hello5.txt", "rw");    FileChannel channel2 = raf2.getChannel();    channel2.write(byteBuffers);}

选择器


  • 传统的阻塞IO方式在数据被读取或写入时,该线程在此期间不能执行其他任务。而NIO的非阻塞方式在没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。

  • 选择器Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读、写以及接收事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。也就是说选择器监控这些通道的IO状况(连接接入、读、写以及接收事件)。

  • 选择器(Selector)是SelectableChannle 对象的多路复用器,Selector 可以同时监控多个SelectableChannel的IO状况,也就是说,利用 Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。

  • SelectableChannle是可通过Selector复用的通道,它是所有支持就绪检查的通道类的父类,提供了实现通道的可选择性所需要的公共方法。注意:FileChannel类没有继承SelectableChannel因此不是可选通道。

739ede9341c2969c449554957326b36e.png

  • 选择键(SelectionKey):选择键封装了特定的通道SelectableChannel与特定的选择器Selector的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。

  • 注册方法详解,第二个参数可以理解为选择器对通道的监听事件。多个监听事件时可以使用位或‘|’连接。

//sel - 要注册该频道的选择器  ops - 为结果键设置的兴趣//SelectionKey中有四个事件:OP_CONNECT、OP_ACCEPT、OP_READ、OP_WRITEpublic final SelectionKey register(Selector sel, int ops) throws ClosedChannelException
  • 代码实例。

    • 阻塞式IO

//阻塞式IO@Testpublic void testClient1() throws IOException {    //1、获取通道    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));    //2、获取文件通道    FileChannel fileChannel = FileChannel.open(Paths.get("爱情与友情.jpg"), StandardOpenOption.READ);    //3、创建缓冲区    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);    //4、读取本地文件,发送到服务器    while (fileChannel.read(byteBuffer) != -1){        byteBuffer.flip();        socketChannel.write(byteBuffer);        byteBuffer.clear();    }    //5、关闭连接以进行写入,而不关闭通道。    socketChannel.shutdownOutput();    //6、接收服务器的反馈    int len = 0;    while ((len = socketChannel.read(byteBuffer)) != -1){        //7、读写切换        byteBuffer.flip();        System.out.println(new String(byteBuffer.array(),0,len));        //8、清除缓存        byteBuffer.clear();    }    //9、关闭通道    socketChannel.close();    fileChannel.close();;}@Testpublic void testServer1() throws IOException {    //1、获取通道    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();    //2、打开文件通道    FileChannel fileChannel = FileChannel.open(Paths.get("爱情与友情9.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);    //3、将通道的套接字绑定到本地地址,并配置套接字以监听连接。    serverSocketChannel.bind(new InetSocketAddress(9999));    //4、接收客户端的连接    SocketChannel socketChannel = serverSocketChannel.accept();    //5、创建缓冲区    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);    //6、读取数据并写入    while(socketChannel.read(byteBuffer) != -1){        byteBuffer.flip();        fileChannel.write(byteBuffer);        byteBuffer.clear();    }    //7、发送数据到客户端    byteBuffer.put("我是服务端,我已经成功接收到数据".getBytes());    byteBuffer.flip();    //8、将数据写入到通道    socketChannel.write(byteBuffer);    //9、关闭通道    socketChannel.close();    fileChannel.close();    serverSocketChannel.close();}
    • 非阻塞式IO

//非阻塞式IO@Testpublic void testclient2() throws IOException {    //1、获取通道    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));    //2、切换非阻塞模式    socketChannel.configureBlocking(false);    //3、分配缓冲区    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);    //4、发送数据给服务端    byteBuffer.put(("客户端传送时间数据:" + new Date()).getBytes());    byteBuffer.flip();    socketChannel.write(byteBuffer);    byteBuffer.clear();    //5、关闭通道    socketChannel.close();}@Testpublic void testServer2() throws IOException {    //1. 获取通道    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();    //2. 切换非阻塞模式    serverSocketChannel.configureBlocking(false);    //3. 绑定连接    serverSocketChannel.bind(new InetSocketAddress(8888));    //4. 获取选择器    Selector selector = Selector.open();    //5. 将通道注册到选择器上, 并且指定“监听接收事件”    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);    //6. 轮询式的获取选择器上已经“准备就绪”的事件    while (selector.select()>0){        //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”        Iterator iterator = selector.selectedKeys().iterator();        while (iterator.hasNext()){            //8. 获取准备“就绪”的事件            SelectionKey selectionKey = iterator.next();            //9. 判断具体是什么事件准备就绪            if(selectionKey.isAcceptable()){                //10. 若“接收就绪”,获取客户端连接                SocketChannel socketChannel = serverSocketChannel.accept();                //11. 切换非阻塞模式                socketChannel.configureBlocking(false);                //12. 将该通道注册到选择器上                socketChannel.register(selector,SelectionKey.OP_READ);            } else if(selectionKey.isReadable()){                //13. 获取当前选择器上“读就绪”状态的通道                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();                //14. 创建缓冲区 读取数据                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);                int len = 0;                while ((len = socketChannel.read(byteBuffer))>0){                    byteBuffer.flip();                    System.out.println(new String(byteBuffer.array(),0,len));                    byteBuffer.clear();                }            }            //15. 取消选择键 SelectionKey            iterator.remove();        }    }}

NIO2知识整理


  • 新增Path接口,Paths工具类,Files工具类。这些接口和工具类对NIO中的功能进行了高度封装,大大简化了文件系统的IO编程。

  • java.nio.file.Path接口代表一个平台无关的平台路径,描述了目录结构中文件的位置。

  • java.nio.file.Paths仅由静态方法组成,通过转换路径字符串返回Path或URI 。

  • java.nio.file.Files用于操作文件或目录的工具类。

往期推荐 f110b58c2add1666cf21bedd15ff26ab.gif

d27e4456d294691da3174cb4107f3be2.png

2a5e9720f4d82a6f7722cc5375878659.png

#   java高级之泛型②    #   java高级之IO流①    # 

#   集合源码分析③      #   java高级之泛型①    # 

#   java高级之集合①    #   java高级之集合②    #

#   java高级之注解       #   设计模式之单例模式 #

#   java高级之反射①    #   java高级之反射②    #

#   java高级之多线程    #   java高级之枚举       #

END

博客园地址  :                  https://www.cnblogs.com/manongxiao

0563110c7c2a391016a0c6fbefa70e0e.png

       落木萧潇公众号

       文  |  言  熙

       编辑 | PeNg.Fall

fe912549d849f501f701be73f9d2cfb1.gif

球分享

fe912549d849f501f701be73f9d2cfb1.gif

球点赞

fe912549d849f501f701be73f9d2cfb1.gif

球在看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值