Java之NIO,BIO,AIO

笑着哭i 提交于 2020-12-16 08:40:17

Hollis知识星球的一些学习笔记,有兴趣的朋友可以微信搜一下

什么是NIO

什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。 在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。

什么是NIO? NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。 一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。

什么是AIO

Java AIO即Async非阻塞,是异步非阻塞的IO。

什么是BIO

Java BIO即Block I/O , 同步并阻塞的IO。 BIO就是传统的java.io包下面的代码实现。

AIO,BIO,NIO的联系和区别

BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。

NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。

AIO ( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。

BIO实现文件读写

直接代码实现:

package com.wzlove.bio;

import java.io.Serializable;

/**
 * @author 王智
 * @date 2018/7/31
 * @描述 用户实体类
 */
public class User implements Serializable {


    private static final long serialVersionUID = -7521946815608679573L;
    private  String name;

    private int age;

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}




package com.wzlove.bio;

import java.io.*;

/**
 * @author 王智
 * @date 2018/7/31
 * @描述 用来测试BIO
 * BIO : Java BIO即Block I/O , 同步并阻塞的IO。 测试BIO
 * 其实BIO 也就是我们时常说的序列化流和反序列化流
 */
public class BIODemo {

    public static void main(String[] args){

        // 使用输出流进行写入到文件中
        User user = new User("王智",23);
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"))) {
            // 写入
            oos.writeObject(user);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 使用输入流进行读取
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"))) {
            // 进行信息的读取
            User user1 = (User)ois.readObject();
            System.out.println(user1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

需要注意的是,写入的读取的对象必须实现Serializable接口,生成serialVersionUID,还有无参构造,都要存在.

NIO实现文件的读写

package com.wzlove.nio;

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;
import java.nio.charset.Charset;

/**
 * @author 王智
 * @date 2018/7/31
 * @描述 NIO 以块的方式处理数据
 * 使用的方法有:
 *    ByteBuffer的常用方法:
     *   static ByteBuffer allocate(int capacity) 分配一个新的字节缓冲区。
     *   int capacity() 返回此缓冲区的容量。
     * 	 int position() 返回此缓冲区的位置。
     * 	 int read(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
 *    Charset的常用方法:
     * 	  Charset forName(String charsetName) 返回名为charset的charset对象。
     * 	  ByteBuffer encode(String str) 在此字符集中将字符串编码为字节的便捷方法。
 */
public class NIODemo {
    public static void main(String[] args) {
        writeNIO();
        System.out.println("===========================================/");
        readNIO();
    }

    /**
     * 步骤 :
     *  1.使用输入流创建文件通道
     *  2. 创建字节缓冲区
     *  3. 开始读取内容
     *  4. 将读取的内容转化为字节数组,使用System.out的write方法输出
     */
    public static void readNIO(){
        try (
                // c创建文件通道
                FileChannel fc = new FileInputStream("nio.txt").getChannel()
        ) {
            // 创建字节缓冲区(static ByteBuffer allocate(int capacity)分配一个新的字节缓冲区。指定缓冲区的大小  )
            ByteBuffer bb = ByteBuffer.allocate(100);
            // 输出缓冲区的大小
            System.out.println("输入缓冲区的大小为 : " + bb.capacity());
            // 输出缓冲区的限制大小
            System.out.println("输入缓冲区的限制大小为 : "  + bb.limit());
            // 输出缓冲区的位置
            System.out.println("输入缓冲区的位置 : " + bb.position());
            // 进行文件的读取
            int length = -1;
            while((length = fc.read(bb)) != -1){
                /*
                 * 注意,读取后,将位置置为0,将limit置为容量, 以备下次读入到字节缓冲中,从0开始存储
                 */
                bb.clear();
                byte[] bytes = bb.array();
                System.out.write(bytes,0,length);
                System.out.println();
                // 输出缓冲区的大小
                System.out.println("输入缓冲区的大小为 : " + bb.capacity());
                // 输出缓冲区的限制大小
                System.out.println("输入缓冲区的限制大小为 : "  + bb.limit());
                // 输出缓冲区的位置
                System.out.println("输入缓冲区的位置 : " + bb.position());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 步骤:
     *  1. 使用输出流创建文件通道
     *  2. 创建字节缓冲区
     *  3. 开始进行写入
     */
    public static void writeNIO(){

        try (
                // 创建FileChannel通道(使用try-with-resouce的形式)
                FileChannel fc = new FileOutputStream("nio.txt").getChannel()

        ) {
            // 按照指定的编码和内容创建字节缓冲区
            ByteBuffer  bb = Charset.forName("UTF-8").encode("你好啊,世界,我爱你,世界");
            // 输出初始化容量
            System.out.println("初始化容量 : " + bb.capacity());
            // 输出缓冲区的限制大小
            System.out.println("缓冲区的限制为 : " + bb.limit());
            int len = 0;
            // 标准的写入文件的代码
            while((len = fc.write(bb)) != 0){
                /*
                 * 注意,这里不需要clear,将缓冲中的数据写入到通道中后 第二次接着上一次的顺序往下读
                 */
                System.out.println("写入的长度为 : " + len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

AIO实现文件的读写(不是特别理解)

public class AioWrite {


    public static void main(String[] args) throws Exception {
        // AsynchronousFileChannel用于读取,写入和操作文件的异步通道。当通过调用此类定义的open方法之一来打开文件时,将创建异步文件通道。
        // static AsynchronousFileChannel open(Path file, OpenOption... options) 打开或创建用于读取和/或写入的文件,返回异步文件通道以访问该文件。
        /*
        StandardOpenOption枚举类:
            APPEND 如果文件打开 WRITE访问,则字节将被写入文件的末尾而不是开头。
            CREATE  创建一个新文件(如果不存在)。
            CREATE_NEW 创建一个新的文件,如果该文件已经存在失败。
            DELETE_ON_CLOSE 关闭时删除。
            DSYNC 要求将文件内容的每次更新都与底层存储设备同步写入。
            READ 打开阅读权限。
            SPARSE 稀疏文件
            SYNC 要求将文件内容或元数据的每次更新都同步写入底层存储设备。
            TRUNCATE_EXISTING 如果文件已经存在,并且打开 WRITE访问,则其长度将截断为0。
            WRITE 打开以进行写入。
         */
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                Paths.get("asynchronous.txt"), StandardOpenOption.READ,
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        /*
            CompletionHandler 用于消除异步I / O操作结果的处理程序。
            在此包中定义的异步通道允许指定完成处理程序以消耗异步操作的结果。 当I / O操作成功完成时,
            将调用completed方法。 如果I / O操作失败,则调用failed方法。 这些方法的实现应该及时完成,
            以避免将调用线程调度到其他完成处理程序。
        */
        CompletionHandler<Integer, Object> handler = new CompletionHandler<>() {

            @Override
            public void completed(Integer result, Object attachment) {
                System.out.println("Attachment: " + attachment + " " + result
                        + " bytes written");
                System.out.println("CompletionHandler Thread ID: "
                        + Thread.currentThread().getId());
            }

            @Override
            public void failed(Throwable e, Object attachment) {
                System.err.println("Attachment: " + attachment + " failed with:");
                e.printStackTrace();
            }
        };

        System.out.println("Main Thread ID: " + Thread.currentThread().getId());
        // abstract <A> void write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer,? super A> handler)
        // 从给定的缓冲区向给定的文件位置开始,向该通道写入一个字节序列。
        // static ByteBuffer wrap(byte[] array) 将一个字节数组包装到缓冲区中。
        fileChannel.write(ByteBuffer.wrap("Sample".getBytes()), 0, "First Write",
                handler);
        // 第一次写入Sample,第二次写入也是从索引0位置开始的,所以讲Sam覆盖掉了,变为Box,所以结果为Boxple
        fileChannel.write(ByteBuffer.wrap("Box".getBytes()), 0, "Second Write",
                handler);

    }
}




public class AioReader {

    public static void main(String[] args) throws Exception {
        // Path可用于在文件系统中定位文件的对象。 它通常表示系统相关的文件路径
        Path file = Paths.get("asynchronous.txt");
        // AsynchronousFileChannel是用于读取,写入和操作文件的异步通道。
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
        // 创建字节缓冲区,指定缓冲区的大小
        ByteBuffer buffer = ByteBuffer.allocate(100_000);
        // Future接口,A Future计算的结果。 提供方法来检查计算是否完成,等待其完成,并检索计算结果
        Future<Integer> result = channel.read(buffer, 0);
        // boolean isDone() 返回 true如果任务已完成
        while (!result.isDone()) {
            // 暂时不了解
            ProfitCalculator.calculateTax();
        }
        // V get() 等待计算完成,然后检索其结果。
        Integer bytesRead = result.get();
        // 输出文件内容的字节长度
        System.out.println("Bytes read [" + bytesRead + "]");
    }
}

class ProfitCalculator {
    public ProfitCalculator() {
    }
    public static void calculateTax() {
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!