NIO详解


前期处理

对应的思维导图地址: https://www.processon.com/view/link/62247e810e3e74108ca1b5d7

对应的 Github地址: https://github.com/yuejianli/NIO

依赖

全局 pom.xml 依赖

<dependencies>


    <dependency\>

        <groupId\>junit</groupId\>

        <artifactId\>junit</artifactId\>

        <version\>4.13</version\>

    </dependency\>

    <dependency\>

        <groupId\>log4j</groupId\>

        <artifactId\>log4j</artifactId\>

        <version\>1.2.17</version\>

    </dependency\>

    <dependency\>

        <groupId\>org.slf4j</groupId\>

        <artifactId\>slf4j-log4j12</artifactId\>

        <version\>1.7.32</version\>

    </dependency\>



    <!--添加 lombok 依赖,用于日志打印-->

    <dependency\>

        <groupId\>org.projectlombok</groupId\>

        <artifactId\>lombok</artifactId\>

        <version\>1.18.22</version\>

    </dependency\>

    <!--用于统计时间-->

    <dependency\>

        <groupId\>cn.hutool</groupId\>

        <artifactId\>hutool-all</artifactId\>

        <version\>5.7.20</version\>

    </dependency\>

</dependencies\>

日志配置

log4j.properties



# priority  :debug<info<warn<error

#you cannot specify every priority with different file for log4j

log4j.rootLogger\=debug,stdout,info,debug,warn,error 



#console

log4j.appender.stdout\=org.apache.log4j.ConsoleAppender 

log4j.appender.stdout.layout\=org.apache.log4j.PatternLayout 

log4j.appender.stdout.layout.ConversionPattern\= \[%d{yyyy-MM-dd HH:mm:ss a}\]:%p %l%m%n

#info log

log4j.logger.info\=info

log4j.appender.info\=org.apache.log4j.DailyRollingFileAppender 

log4j.appender.info.DatePattern\='\_'yyyy-MM-dd'.log'

log4j.appender.info.File\=./src/com/hp/log/info.log

log4j.appender.info.Append\=true

log4j.appender.info.Threshold\=INFO

log4j.appender.info.layout\=org.apache.log4j.PatternLayout 

log4j.appender.info.layout.ConversionPattern\=%d{yyyy-MM-dd HH:mm:ss a} \[Thread: %t\]\[ Class:%c >> Method: %l \]%n%p:%m%n

项目代码

一. FileChannel

读取文件

 //1. 使用 RandomAccessFile 创建 FileChannel

    //2. 创建缓冲区 ByteBuffer
    
    //3.将数据读到Buffer 里面
    
    //3.1 循环读取,然后反转,写入,清空。



    /\*\*
    
     \* 读文件测试
    
     \*
    
     \* @date 2022/3/24 9:23
    
     \* @author zk\_yjl
    
     \* 读文件测试
    
     \*/
    
    @Test
    
    public void readFileTest() throws IOException {
    
        RandomAccessFile randomAccessFile \=
    
                new RandomAccessFile("F:\\\\Java\\\\NIO\\\\Code\\\\NIO\\\\read.txt", "rw");
    
        //创建通道
    
        FileChannel fileChannel \= randomAccessFile.getChannel();



        //创建缓冲区 通过 allocate 指定大小  一次性读取 1024个字节,不是一个个读
    
        // ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    
        // allocate(4) 时会多读写几回。
    
        ByteBuffer byteBuffer \= ByteBuffer.allocate(4);



        //读取到byteBuffer 缓冲区里面  如果读取不到信息,返回 -1
    
        int readCount \= fileChannel.read(byteBuffer);
    
        while (readCount \> \-1) {
    
            //信息
    
            log.info(">>读取字节数" + readCount);
    
            //调用 flip 进行写入
    
            byteBuffer.flip();
    
            while (byteBuffer.hasRemaining()) {
    
                //读取, 中文会乱码.
    
                log.info(">>>写出数据:" + (char) byteBuffer.get());
    
            }
    
            byteBuffer.clear();
    
            //清空之后,继续读取, 第二回是 -1 ,读取不到数据。
    
            readCount \= fileChannel.read(byteBuffer);
    
        }
    
        fileChannel.close();
    
        randomAccessFile.close();
    
        log.info(">>>读出数据成功");



    }

写入数据到文件

/**
     * 写入数据
     */
    @Test
    public void writeTest() throws IOException {
        RandomAccessFile randomAccessFile =
                new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\write.txt", "rw");
        //1. 获取通道
        FileChannel fileChannel = randomAccessFile.getChannel();
        //2. 定义数据,并写入到 ByteBuffer 里面
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //放置信息
        byteBuffer.clear();
        byteBuffer.put("Hello NIO".getBytes());
        //进行读取转换
        byteBuffer.flip();
        log.info(">>>开始写入数据");
        //将数据写入到 Channel 里面
        while (byteBuffer.hasRemaining()) {
            //写入到 channel 里面
            fileChannel.write(byteBuffer);
        }
        log.info(">>>写入数据完成");
        fileChannel.close();
    }

写入大量数据

/**
     * 写入大量数据
     */
    @Test
    public void writeMoreTest() throws IOException {
        RandomAccessFile randomAccessFile =
                new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\write.txt", "rw");
        //1. 获取通道
        FileChannel fileChannel = randomAccessFile.getChannel();
        //2. 定义数据,并写入到 ByteBuffer 里面
        ByteBuffer byteBuffer = ByteBuffer.allocate(4);
        byte[] bytes = "Hello NIO !!!".getBytes();
        log.info(">>>开始写入数据");
        for (int i = 0; i < bytes.length; i = i + 4) {
            //放置信息
            byteBuffer.clear();
            byteBuffer.put(ArrayUtil.sub(
                    bytes, i, Math.min(
                            i + 4,
                            bytes.length
                    )
            ));
            //进行读取转换
            byteBuffer.flip();
            //将数据写入到 Channel 里面
            while (byteBuffer.hasRemaining()) {
                //写入到 channel 里面
                fileChannel.write(byteBuffer);
            }
            log.info(">>>写入一次");
        }
        log.info(">>>写入数据完成");
        fileChannel.close();
    }

其它方法操作

/**
     * position()   返回当前的位置
     * position(index) 设置当前的位置。
     * 如果超过文件 结束符, 读取时,会返回  -1
     * 写入时,会从 index 处开始写入,将文件增大。会造成 "文件空洞" 现象
     * <p>
     * size () 获取关联的文件 的大小, 与position() 位置无关。 truncate() 截取,会导致 size() 发生改变。
     * <p>
     * truncate(index) 截取, 会将源文件进行截取, size() 会改变。 截取后再读取 position
     *
     * @throws IOException 异常
     */
    @Test
    public void otherTest() throws IOException {
        //读取
        RandomAccessFile randomAccessFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\read.txt", "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();
        //获取对应的位置信息
        log.info(">>>获取文件的大小:" + fileChannel.size());
        log.info(">>获取位置:" + fileChannel.position());
        //设置位置, 即前四个不读
        fileChannel.position(
                fileChannel.position() + 4
        );
​
        log.info(">>>获取文件的大小:" + fileChannel.size());
        //截取,只要前四个。 后面的不要。
        fileChannel.truncate(8);
        log.info(">>>截取文件内容,只要前八个");
        log.info(">>>获取文件的大小:" + fileChannel.size());
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);int readCount = fileChannel.read(byteBuffer);
        while (readCount > -1) {
            log.info(">>>读取信息:" + readCount);
            byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                log.info(">>获取读取的信息:" + (char) byteBuffer.get());
            }
            byteBuffer.clear();
            readCount = fileChannel.read(byteBuffer);
        }
        //进行关闭
        fileChannel.close();
        randomAccessFile.close();
        log.info(">>>读取文件内容结束");
    }

文件复制1

/**
 * 文件复制, target.transferFrom(sourceChannel,position,count)
 *
 * @throws IOException count 时,数目为 当前 channel 的最大内容数目。
 *                     并且只是,当前接收到的数目。
 */
@Test
public void transferFromTest() throws IOException {
    //定义两个文件流
    RandomAccessFile randomAccessFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\read.txt", "rw");
    FileChannel sourceChannel = randomAccessFile.getChannel();RandomAccessFile copyFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\readCopy.txt", "rw");
    FileChannel targetChannel = copyFile.getChannel();//获取文件的大小
    log.info(">>>>开始进行文件");
    int position = 0;
    long count = sourceChannel.size();//调用,进行复制
    targetChannel.transferFrom(
            sourceChannel, position, count
    );
    log.info(">>>复制文件成功");
}

文件复制2


 /**
     * 文件复制, source.transferTo(position,count,targetChannel)
     *
     * @throws IOException 异常
     */
    @Test
    public void transferToTest() throws IOException {
        //定义两个文件流
        RandomAccessFile randomAccessFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\read.txt", "rw");
        FileChannel sourceChannel = randomAccessFile.getChannel();RandomAccessFile copyFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\readCopy.txt", "rw");
        FileChannel targetChannel = copyFile.getChannel();//获取文件的大小
        log.info(">>>>开始进行文件");
        int position = 0;
        long count = sourceChannel.size();//调用,进行复制
        sourceChannel.transferTo(
                position, count, targetChannel
        );
        log.info(">>>复制文件成功");
    }

依次读取

 /**
     * 会依次读取
     *
     * @throws IOException Scattering Reads 在移动下一个 buffer 前,必须填满当前的 buffer,这也意味着它
     *                     不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,
     *                     消息头必须完成填充(例如 128byte),Scattering Reads 才能正常工作。
     */
    @Test
    public void scatterTest() throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\read.txt", "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();
        //创建 ByteBuffer 数组
        ByteBuffer[] byteBuffers = new ByteBuffer[3];
        byteBuffers[0] = ByteBuffer.allocate(4);
        byteBuffers[1] = ByteBuffer.allocate(4);
        byteBuffers[2] = ByteBuffer.allocate(10);//读取数据到 fileChannel
        log.info(">>>读取数据到 byteBuffers");
        fileChannel.read(byteBuffers);//输出信息
        //写入数据
        log.info(">>> 打印第一个 ByteBuffer 里面的数据");
        //需要转换 flip
        byteBuffers[0].flip();
        while (byteBuffers[0].hasRemaining()) {
            //写入数据
            log.info("读取数据:" + (char) (byteBuffers[0].get()));
        }
        log.info(">>> 打印第二个 ByteBuffer 里面的数据");
        byteBuffers[1].flip();
        while (byteBuffers[1].hasRemaining()) {
            //写入数据
            log.info("读取数据:" + (char) (byteBuffers[1].get()));
        }
        log.info(">>> 打印第三个 ByteBuffer 里面的数据");
        byteBuffers[2].flip();
        while (byteBuffers[2].hasRemaining()) {
            //写入数据
            log.info("读取数据:" + (char) (byteBuffers[2].get()));
        }
        fileChannel.close();
        randomAccessFile.close();
    }

依次写入


/**
     * 会依次写入
     * <p>
     * buffers 数组是 write()方法的入参,write()方法会按照 buffer 在数组中的顺序,将数
     * 据写入到 channel,注意只有 position 和 limit 之间的数据才会被写入。因此,如果
     * 一个 buffer 的容量为 128byte,但是仅仅包含 58byte 的数据,那么这 58byte 的数
     * 据将被写入到 channel 中。因此与 Scattering Reads 相反,Gathering Writes 能较
     * 好的处理动态消息
     *
     * @throws IOException 异常
     */
    @Test
    public void gatherTest() throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile("F:\\Java\\NIO\\Code\\NIO\\write.txt", "rw");
        FileChannel fileChannel = randomAccessFile.getChannel();
        //创建 ByteBuffer 数组
        ByteBuffer[] byteBuffers = new ByteBuffer[3];
        byteBuffers[0] = ByteBuffer.allocate(4);
        byteBuffers[1] = ByteBuffer.allocate(4);
        byteBuffers[2] = ByteBuffer.allocate(10);//读取数据到 fileChannel   不能超过对应的长度
        byteBuffers[0].put("Hell".getBytes());
        byteBuffers[1].put("NIOO".getBytes());
        byteBuffers[2].put("!!!!!!".getBytes());
        log.info(">>>写入数据到 byteBuffers");
        //不要忘记 调用  flip () 处理
        byteBuffers[0].flip();
        byteBuffers[1].flip();
        byteBuffers[2].flip();
        fileChannel.write(byteBuffers);
        log.info(">>写入数据成功");
        fileChannel.close();
        randomAccessFile.close();
    }

ServerSocketChannel

简单的配置


 /**
     * 测试处理
     *
     * @throws Exception 异常
     */
    @Test
    public void simpleTest() throws Exception {
        //1. 准备数据  ByteBuffer  包装到缓冲区
        ByteBuffer byteBuffer = ByteBuffer.wrap(
                "Hello NIO!!!".getBytes()
        );
        //创建 ServerSocketChannel  .open ()
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //进行绑定 指定的端口
        serverSocketChannel.socket().bind(
                new InetSocketAddress(9999)
        );
        //配置为异步
上一篇:Python 从0开始 一步步基于Django创建项目(6)实现一个简易网站


下一篇:新版 Redis 不再“开源”,对使用者都有哪些影响?