【2023】NIO从零到一入门学习——入门篇之-Channel & Buffer(2)

一、ByteBuffer

1、基本使用代码

  1. 向buffer写入数据,例如调用channelread(buffer)
  2. 调用flip()切换至读模式
  3. 从buffer读取数据,例如调用buffer.get()
  4. 调用clear() 或cornpact()切换至写模式
  5. 重复1-4
public static void main(String[] args) {
//        FileChannel
//        1、输入输出流  2.RandomAccessFile  data.txt
        try(FileChannel channel  = new FileInputStream("D:\\1zheng\\dai\\netty\\mode01\\data.txt").getChannel()){
//          准备缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);  //10个字节
            while (true) {
//                从channel 读取数据,向buffer写入
                int read = channel.read(buffer);
                log.debug("读取到的字节数:{}", read);
                if (read == -1) break; //没有内容时退出
                
//                打印内容
                buffer.flip();  //切换读模式
                while (buffer.hasRemaining()) {  //是否还有剩余数据
                    byte b = buffer.get();  
                    log.debug("实际字节:{}", (char) b);
                }
                buffer.clear();  //切换写模式
            }
        }catch (IOException e){

        }
    }

ByteBuffer有以下重要属性

  • capacity :容量
  • position : 读写下标
  • limit :读写限制

流程

在这里插入图片描述

在这里插入图片描述

2、ByteBuffer常见方法

分配空间

可以使用 allocate 方法为ByteBuffer分配内存,也可以使用*allocateDirect方法*

allocate :分配的是是java堆内存,读写效率低,受到GC的影响

allocateDirect:直接内存,读写效率高(少一次拷贝),不会受到GC的影响,分配的效率低

读写操作

clear() :从0下标开始写

cornpact():压缩已有的空间,从写入到的位置继续写

get():从position 位置开始读;

get(0):读取0下标的值;

rewind():把position 移动到0下标;

mark():标记当前position 下标

reset()position移动到mark()标记的下标去;

字符串转换

put("hello".getBytes());

StandardCharsets.UTF_8.encode("hello");

ByteBuffer.wrap("hello".getBytes());

public static void main(String[] args) {
//        1、字符串转为ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(16);

        buffer.put("hello".getBytes());    //该方法需要去手动转为读模式
        buffer.flip();
        System.out.println(buffer);
        System.out.println((char) buffer.get());

//        下面两种方法可以自动转为读模式
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
        System.out.println(buffer2);
        System.out.println((char) buffer2.get());

        ByteBuffer buffer1 = ByteBuffer.wrap("hello".getBytes());
        System.out.println(buffer1);
        System.out.println((char) buffer1.get());

//        2、把ByteBuffer转为字符串
        String s = StandardCharsets.UTF_8.decode(buffer2).toString();
        System.out.println(s);
    }

分散读取,集中写入到磁盘

public static void main(String[] args) {
    ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
    ByteBuffer buffer3 = StandardCharsets.UTF_8.encode("hello");
    ByteBuffer buffer4 = StandardCharsets.UTF_8.encode("你好");
    try(FileChannel channel = new RandomAccessFile("words2.txt","rw").getChannel()){
        channel.write(new ByteBuffer[]{buffer2,buffer3,buffer4});
    }catch (Exception e){}
}

解决网络编程中ByteBuffer常见的粘包,半包问题

public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.put("Hello,world\nI'm zhangsan\nHo".getBytes());
        split(buffer);  //会先把粘包的读取完
        buffer.put("w are you?\n".getBytes());
        split(buffer);   //最后在把半包的拼接读取掉
    }

    private static void split(ByteBuffer buffer) {
        buffer.flip();
        for (int i = 0; i < buffer.limit(); i++) {
//            通过\n找到一条完整消息
            if (buffer.get(i)=='\n'){
                int length = i+1-buffer.position();
//                  创建指定大小的缓冲区
                ByteBuffer target = ByteBuffer.allocate(length);
//                把完整数据写入新的ByteBuffer中
                for (int j = 0; j < length; j++) {
                    target.put(buffer.get());
                }
                target.flip();
                String s = StandardCharsets.UTF_8.decode(target).toString();
                System.out.println(s);
                System.out.println(buffer);
            }
        }
        buffer.compact();
    }

二、FileChannel

⚠️注意

FileChannel只能工作在阻塞模式下

文件拷贝:通过FileChannel把文件内容拷贝到新的位置

该方式有个缺点一次性只能传输2G的大小

transferTo():该方法采用的是一种零拷贝的技术,直接再内核缓冲区把数据发送到目标通道,减少了从内核态切换到用户态再切换到内核态的上下文切换操作,且直接发送数据到目标通道。效率会高很多。

public static void main(String[] args) {
        try (FileChannel from = new FileInputStream("D:\\1zheng\\dai\\netty\\mode01\\data.txt").getChannel();
             FileChannel to = new FileOutputStream("D:\\1zheng\\dai\\netty\\mode01\\tt01.txt").getChannel()
        ){
//            效率比传统io输入输出的效率高底层会利用操作系统的零拷贝进行优化
            /*
             * from读取文件通道,(开始读取下标,结束下标,读取到通道)
						 *   一次最多传输 2G 的数据
             */
            from.transferTo(0,from.size(),to);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

如果文件比较大时,可以循环传输

public static void main(String[] args) {
        try (FileChannel from = new FileInputStream("D:\\1zheng\\dai\\netty\\mode01\\data.txt").getChannel();
             FileChannel to = new FileOutputStream("D:\\1zheng\\dai\\netty\\mode01\\tt01.txt").getChannel()
        ){
//            效率比传统io输入输出的效率高底层会利用操作系统的零拷贝进行优化
            /*
             * 一次最多传输 2G 的数据
             */
            long size = from.size();
//            left 变量代表还剩下多少字节
            for (long left = size; left>0;){
                System.out.println("position:"+(size - left) + " left:" +left);
//                from读取文件通道,(开始读取下标,结束下标,读取到通道)
                left -= from.transferTo((size - left), left, to);   //方法返回值是本次传输的大小
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

Path

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7TmOj760-1688550887220)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/352bbd17-bc63-43f0-8389-1859906d280d/Untitled.png)]

路径正常化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryCwCsry-1688550887222)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/51b5a3af-4dea-4957-8766-d6e0bff180e7/Untitled.png)]

Files

检查文件是否存在,创建目录

在这里插入图片描述

创建多级

在这里插入图片描述

拷贝文件
在这里插入图片描述

public static void main(String[] args) {
        Path path1 = Paths.get("D:\\1zheng\\dai\\netty\\mode01\\data.txt");
        Path path2 = Paths.get("D:\\1zheng\\dai\\netty\\mode01\\tt02.txt");
        try{
//            直接拷贝,但如果已有,则报错  FileAlreadyExistsException
            Files.copy(path1,path2);
//            拷贝,如果已经有,则覆盖
            Files.copy(path1,path2, StandardCopyOption.REPLACE_EXISTING);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

移动文件

public static void main(String[] args) throws IOException {
        Path path1 = Paths.get("D:\\1zheng\\dai\\netty\\mode01\\data.txt");
        Path path2 = Paths.get("data.txt");
//        StandardCopyOption.ATOMIC_MOVE用于保证文件的原子性
        Files.move(path2,path1, StandardCopyOption.ATOMIC_MOVE);
    }

获取文件属性:readAttributes

  • 具体属性名称:

🅾️ 获取文件目录:Files.*walkFileTree()*

AtomicInteger :计数器

public static void test01(Path path) throws IOException {
//        计数器
        final AtomicInteger dirCount = new AtomicInteger();
        final AtomicInteger fileCount = new AtomicInteger();
//        获取文件夹目录
        Files.walkFileTree(path,new SimpleFileVisitor<Path>(){
            //            遍历文件夹
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {

                System.out.println("====>"+dir);

                dirCount.incrementAndGet();
                return super.preVisitDirectory(dir, attrs);
            }
            //            遍历文件
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file);
                fileCount.incrementAndGet();
                return super.visitFile(file, attrs);
            }
        });
        System.out.println("dir count: "+dirCount);
        System.out.println("file count: "+fileCount);
    }

文件夹拷贝

public static void main(String[] args) throws IOException {
        String source = "D:\\data";
        String target = "D:\\data202";

        Files.walk(Paths.get(source)).forEach(path->{
            try {
                String targetName = path.toString().replace(source, target);
//                判断该路径是否是文件夹
                if (Files.isDirectory(path)) {
//                    新建文件夹
                    Files.createDirectory(Paths.get(targetName));
                }
//                判断该路径是否是文件
                else if (Files.isRegularFile(path)){
//                    拷贝文件
                    Files.copy(path , Paths.get(targetName));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值