当前位置: 首页 > article >正文

解析NIO

NIO 是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

NIO有三大组件:

 1、channel:数据传输的通道,读写数据的双向通道

 2、buffer:内存缓冲区,暂存channel中的数据

 3、selector:选择器

下面先简单解释一些selector:

  一开始我们是采用的多线程设计解决多个socket连接的,一个子线程只能管理一个socket

缺点是:1、内存占用高;2、线程上下文切换成本高;3、只适合连接数较少的情况

后面改进了多线程设计,有了线程池设计:一个线程可以管理多个socket

缺点是:1、堵塞模式下,线程仅能处理一个socket的连接;2、仅适用于短连接的情况下。

所以就有了selector来解决上述问题

selector作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非堵塞模式下,不会让线程吊死在一个channel上,适合连接数特别多,但流量低的场景。

下面再来看看Buffer中的byteBUffer

byteBuffer有三个重要属性:1、capaticy容量;2、position 读写指针位置;3、limit 读写限制(限制的字节数)

 

写模式下,limit默认与capacity保持一致,读模式下,position与实际存的字节数一致

创建ByteBuffer实例有俩种方法,不用new创建:

//准备缓冲区   划分一块内存作为缓冲区 固定大小为10字节,不可改动

//使用的堆内存 ,读写效率较低,受到GC影响,GC为虚拟机的垃圾回收机制
ByteBuffer buffer=ByteBuffer.allocate(10); 

//或者  直接内存,读写效率较高(少一次数据的拷贝),系统内存不受GC影响;分配内存效率较低,使用不当可能造成内存泄漏
ByteBuffer buffer=ByteBuffer.allocateDirect(10);

byteBuffer的各种方法:

package org.example;

import java.nio.ByteBuffer;
import static org.example.ByteBufferUtil.debugAll;


//各种方法
public class TestByteBufferReadWrite {
    public static void main(String[] args) {
        /*ByteBuffer buffer = ByteBuffer.allocate(10);
        //写模式
        buffer.put((byte) 0x61);//'a'

        buffer.put(new byte[]{0x62,0x62,0x63}); //b c d

        //读模式
        buffer.flip();
        System.out.println(buffer.get());*/

        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'a','b','c','d'});
        buffer.flip();

        //get方法会让position指针向后移,
        // 如果想读重复数据,可以用rewind方法使position重置为0,或者调用get(int i)获取索引i处数据,但不会移动指针
        /*buffer.get(new byte[4]);
        debugAll(buffer);
        buffer.rewind();
        System.out.println((char)buffer.get());*/

        //mark & reset
        //mark 做一个标记,记录position的位置,reset是将position重置到mark的位置。
//        System.out.println((char) buffer.get());
//        System.out.println((char) buffer.get());
//        buffer.mark();//加标记,于索引为2的位置
//        System.out.println((char) buffer.get());
//        System.out.println((char) buffer.get());
//        buffer.reset();//将position重置到索引2
//        System.out.println((char) buffer.get());
//        System.out.println((char) buffer.get());

        //get(i)但不会移动指针位置
        System.out.println((char) buffer.get(3));
    }
}

下面了解一下文本编程FileChannel :只能工作在堵塞模式下

不能直接打开FileChannel,必须通过FileInputStream、FileOutputStream或者RandomAccessFile来获取FileChannel,它们都有getChannel方法。

  1、通过FileInputStream获取的channel只能读

  2、通过FileOutputStream获取的channel只能写

  3、通过RandomAccrssFile获取的channel是否能读写根据构造RandomAccessFile时的读写模式确定。

package org.example;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class byteBuffer {
    public static void main(String[] args) {

            //因为分配给buffer的内存是有限的,所以一般要读多次才能读完。
            ByteBuffer buffer=ByteBuffer.allocate(10);
            //从channel中读取数据,向buffer中写入
            while(true) {//读多次
                int len = channel.read(buffer); //返回读取的字节数,如果返回值为-1 表示已经读完了   buffer.put();
                if(len==-1)
                    break;
                //打印buffer
                buffer.flip();//切换为读模式
                while (buffer.hasRemaining()) {//是否还有剩余
                    byte b = buffer.get();//一次读一个字节    channel.write(buffer);
                    System.out.println((char) b);
                }
                //切换为写模式
                buffer.clear();
            }
        } catch (IOException e) {
        }
    }
}

 

Path 用来表示文件路径,Paths是工具类,用来获取Path实例

Path source = Paths.get("d:\\1.txt");//绝对路径 
Path source = Paths.get("d:/1.txt");//绝对路径 
Path source = Paths.get("d:\\data","projects");//代表d://data/projects
//创建一级目录
Path path = Paths.get("data.txt");
Files.createDirectory(path);
//如果目录存在,会抛异常FileAlreadyExistException
//创建多级目录同样会抛异常

//创建多级目录
Path path = Paths.get("data.txt");
Files.createDirectories(path);

拷贝文件

Path source = Paths.get("1/data.txt");
Path target = Paths.get("2/data.txt");
Files.copy(source,target);
//如果target文件已存在,会抛异常
//如果想覆盖了已存在target文件的内容
Files.copy(source,target,StandardCopyOption.REPALCE_EXISTING);

1、transferTo 多次拷贝文件内容

package org.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

//数据传输
public class TestTransferTo {
    public static void main(String[] args) {
        try (
                FileChannel from = new FileInputStream("data.txt").getChannel();
                FileChannel to = new FileOutputStream("to.txt").getChannel();
                ) {
            //比用文件输入输出流效率要高,底层会利用操作系统的零拷贝进行优化,单次最大传输量为2g,所以我们可以传输多次。
            //单次传输
            //from.transferTo(0,from.size(),to);

            //多次传输
            long size =from.size();
            for(long length=size;length>0;){
                length-=from.transferTo(size-length,length,to);
            }
        } catch (IOException e) {
            e.printStackTrace();
        };
    }
}

2、copy拷贝多级目录

package org.example;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestFilesCopy {
    public static void main(String[] args) throws IOException {
        //拷贝多级目录
        String source="...1";//源文件
        String target="...2";//目标文件

        Files.walk(Paths.get(source)).forEach(path -> { //path 即为遍历的源文件的各个子文件
            try {
                String targetName=path.toString().replace(source,target);//声明要创建的目录名或文件名
                //判断当前路径是否为目录
                if(Files.isDirectory(path)){ //为目录
                    Files.createDirectory(Paths.get(targetName));
                }else{ //为文件
                    Files.copy(path,Paths.get(targetName));
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

 

拷贝文件用transferTo或copy,这俩个效率高.

分散读写:将一个channel中的内容分散读取到多个ByteBuffer中,这多个buffer存的内容共同组成一个channel的内容

集中写入:将多个ByteBuffer中的内容写入到同一个channel中

package org.example;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;


public class TestScatteringReads {
    public static void main(String[] args) {
        //分散读取
        /*try (FileChannel channel = new RandomAccessFile("words.txt", "r").getChannel()) {
            ByteBuffer b1 = ByteBuffer.allocate(3);
            ByteBuffer b2 = ByteBuffer.allocate(3);
            ByteBuffer b3 = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{b1,b2,b3});

        } catch (IOException e) {
        }*/

        //集中写入
        ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
        ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
        ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");

        try (FileChannel channel = new RandomAccessFile("words.txt", "rw").getChannel()) {
            channel.write(new ByteBuffer[]{b1,b2,b3});
        } catch (IOException e) {
        };
    }
}

删除文件/目录

Path target = Paths.get("1.txt");
Files.delete("target");//该方法也可以删目录,如果目录中还有内存,会报错
//如果文件不存在会报错

所以当要删除一整个文件夹时,需要我们对整个文件夹遍历一遍

walkFileTree用于遍历文件夹

package org.example;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;

// 遍历文件夹
public class TestFilesWalkFileTree {
    public static void main(String[] args) throws IOException {
        //删除多级目录
        //Files.delete(Paths.get("")); //只能删文件夹为空的目录
        //需要我们先删子文件夹,在删本文件夹,从里到外
        Files.walkFileTree(Paths.get(""),new SimpleFileVisitor<Path>(){

            @Override //visitFile方法为访问到文件时触发
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file);//打印文件名称

                Files.delete(file);
                return super.visitFile(file, attrs);
            }

            @Override //访问完文件后触发
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                System.out.println("<===退出"+dir);

                Files.delete(dir);
                return super.postVisitDirectory(dir, exc);
            }
        });
    }


http://www.kler.cn/news/362512.html

相关文章:

  • 【信息论基础第六讲】离散无记忆信源等长编码包括典型序列和等长信源编码定理
  • SpringBoot3 + MyBatisPlus 快速整合
  • 如何开启华为交换机 http
  • 10.23六级翻译
  • 插入数据时遇到主键重复问题怎么办?——insert into数据库技巧 (insert into主键重复数据库)
  • AI时代,谷歌会像当年的IBM一样陨落吗?
  • 如何在 JavaScript 项目中限制Node.js版本
  • 【动手学电机驱动】 TI InstaSPIN-FOC(7)Lab05b 速度环控制
  • RabbitMQ进阶_可靠性
  • 大数据新视界 --大数据大厂之 Snowflake 在大数据云存储和处理中的应用探索
  • 网络安全的挑战与对策:从技术防御到综合治理的全方位分析
  • 阵痛中转型,商汤瘦身背后的AI真相
  • js如何获取一个object的第一条数据
  • Java设计模式——适配器模式
  • 开发面试题-更新中...
  • ansible.builtin 和 ansible.posix的区别
  • leetcode hot100 之【LeetCode 141. 环形链表】 java实现
  • sql注入 --二次注入堆叠注入文件读取getshell
  • Shiro 授权(Authorization)总结
  • swagger讲解
  • 集群Spring定时只执行一次
  • 查收查引常用数据库——万方
  • 矩阵基础知识
  • Docker容器间链路管理
  • C++学习笔记----9、发现继承的技巧(二)---- 重用目的的继承
  • 数据库产品中审计与日志(Auditing and Logging)的功能简介