Java NIO学习系列六:Java中的IO模型

Wesley13
• 阅读 514

  前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型:

  • 阻塞I/O(blocking IO)
  • 非阻塞I/O(nonblocking IO)
  • I/O多路复用(IO multiplexing)
  • 异步I/O(asynchronous IO)

但是前面总结的IO模型只是限定在linux下,更偏向于操作系统底层的概念,并没有涉及到Java应用层面,其实Java中也提供了和前面操作系统层面的IO模型相对应的概念,这是本文接下来要讲的重点。

  同样本文会围绕如下几点进行展开:

I/O模型在Java中的对应

适用场景

Java中各种IO模型的使用方式

总结

1. I/O模型在Java中的对应

1.1 阻塞I/O

  传统Java IO提供的面向流的IO操作方式就属于阻塞式的,调用其read()或write()方法的线程会阻塞,直到完成了数据的读写,在读写的过程中线程是什么都做不了的。

1.2 非阻塞I/O

  Java NIO类库提供了多种支持非阻塞模式的类,比如Channel、Buffer,可以将其设置为非阻塞模式,线程向channel请求读数据时,只会获取已经就绪的数据,并不会阻塞以等待所有数据都准备好,这样在数据准备的阶段线程就能够去处理别的事情,这就是非阻塞式读,对于写数据是一样的。

这里和上面阻塞的区别就是,调用read()或write()方法并不阻塞,而是会立即返回,但是这时候IO操作往往是还没有结束的。

1.3 多路复用

  Java NIO中的Selector允许单个线程监控多个channel,可以将多个channel注册到一个Selector中,然后可以"select"出已经准备好数据的channel,或者准备好写入的channel,然后对其进行读或者写数据,这就是多路复用。

1.4 异步IO

   异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。另一方面,内核会等待数据准备完成,然后将数据复制到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就是说用户线程完全不需要关心实际的整个IO操作了,只需要发起请求就行了,当收到内核的成功信号时就可以直接去使用数据了。这就是和非阻塞式的区别,如果说阻塞式IO是完全手动,非阻塞式IO就是半自动,而异步IO就是全自动,多路复用呢?我觉得可以是半自动冲锋枪^_^

在Java 7中,提供了Asynchronous IO,Java NIO中的AsynchronousFileChannel支持异步模型实现的。

2. 适用场景

BIO方式适用于连接数目比较小且每个连接占用大量宽带,这种方式对服务器资源要求比较高,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,编程比较复杂,JDK1.4开始支持。

AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

3. Java中各种IO模型的使用方式

  前面讲了这么多,即讲了linux下的IO模型,又讲了Java中对这些IO模型的支持,到这里我觉得是时候找一些Java中实际的例子看看,下面就分别用三种IO模型来读写文件。

3.1 通过BIO方式读写文件

public void rwByBIO() {
    BufferedReader br = null;
    FileInputStream in = null;
    FileOutputStream out = null;
    try {
      in = new FileInputStream("test.txt");
      out = new FileOutputStream("testBIO.txt");
      List<Integer> list = new ArrayList();
      int temp;
      while((temp = in.read()) != -1) {
        out.write(temp);
      }
      br = new BufferedReader(new InputStreamReader(new FileInputStream("testBIO.txt")));
      System.out.println(br.readLine());
    }catch(Exception e) {
      e.printStackTrace();
    }finally {
      if(br != null) {
        try {
          br.close();
        }catch(IOException e) {
          e.printStackTrace();
        }
      }
      if(out != null) {
        try {
          out.close();
        }catch(IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  在根目录下准备好文件test.txt,里面写上准备好的内容,比如"黄沙百战穿金甲,不破楼兰终不还",然后跑起来,之后应该会多出一个文件testBIO.txt,里面内容是一样的。我们通过BIO的方式读取test.txt中的内容,同样以BIO的方式写入到testBIO.txt中。

3.2 通过NIO读写文件

public void rwByNIO() {
    FileChannel readChannel = null;
    FileChannel writeChannel = null;
    try {
      readChannel = new RandomAccessFile(new File("test.txt"),"r").getChannel();
      writeChannel = new RandomAccessFile(new File("testNIO.txt"),"rw").getChannel();
      ByteBuffer buffer = ByteBuffer.allocate(10);
      int bytesRead = readChannel.read(buffer);
      while(bytesRead != -1) {
        buffer.flip();
        while(buffer.hasRemaining()) {
          // 写入文件
          writeChannel.write(buffer);
        }
        // 一次写完之后
        buffer.clear();
        bytesRead = readChannel.read(buffer);
      }
    }catch(Exception e) {
      e.printStackTrace();
    }finally {
      if(readChannel != null) {
        try {
          readChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if(writeChannel != null) {
        try {
          writeChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  这里是通过NIO中的FileChannel来读写文件,但是要注意,虽然这一节的标题是说用NIO的方式来读写文件,但是FileChannel并不支持非阻塞模式,所以其实际上还是属于阻塞的,即BIO的方式,只是因为这里为了统一演示读写文件的例子,所以仍然使用NIO中的FileChannel类来完成。

3.3 通过AIO方式读写文件

public void rwByAIO() {
        Path path = Paths.get("test.txt");
        AsynchronousFileChannel fileChannel = null;
        try {
            fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            long position = 0;
            Future<Integer> operation = fileChannel.read(buffer, position);
            while(!operation.isDone());
            buffer.flip();
            Path writePath = Paths.get("testAIO.txt");
            if(!Files.exists(writePath)){
                Files.createFile(writePath);
            }
            AsynchronousFileChannel writeFileChannel = AsynchronousFileChannel.open(writePath, StandardOpenOption.WRITE);

            writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    System.out.println("bytes written: " + result);
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    System.out.println("Write failed");
                    exc.printStackTrace();
                }
            });
          
        }catch(Exception e) {
          e.printStackTrace();
        }
      }

  这个例子中是通过异步地方式来读写文件。当调用了Java NIO中的AsynchronousFileChannel对这种操作提供了支持,当调用其read()方法时会立即返回一个Future对象,通过调用其isDone方法来得知数据是否读取完毕。

4. 总结

  本文结合前文讲到的IO模型,分别对应到Java中的具体类库实现,并通过例子演示了BIO、NIO、AIO三种方式读写文件。

  • 标准Java IO提供的面向流的方式属于BIO模型的实现,在读取的过程中是会阻塞的;
  • Java NIO提供的Channel和Buffer是支持NIO模式的,调用了Channel的读写方法之后可以立即返回,在往Buffer中准备数据的过程中是不阻塞的,线程可以做别的事情,但是从Buffer读写数据是阻塞的;
  • Java NIO中提供的AsynchronousFileChannel支持异步读写文件,当调用了其读写方法之后可以立即返回,只需要等待系统把数据复制到指定位置即可,整个过程都不会阻塞;

参考文献

bio-vs-nio-vs-aio

linux-io

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Wesley13 Wesley13
2年前
NIO
1、简介1.1Java中的IO介绍1.BIO:BlockingIO,同步式阻塞式IO,即传统的IO,是java中最早期的流2.NIO:NonBlockingIO,又称NewIO,同步式非阻塞IO,是JDK1.4提供的流3.AIO:AsynchronousIO,异步是非阻塞IO,可以认为是NIO的二代版
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
4个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这