NIO 基础

NIO 基础

  1. 字符串与ByteBuffer 互转
// 1. 字符串转为 ByteBuff
// 首先分配一个16字节大小的byteBuff
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
// 然后将“你好”的字节数组填充到 byteBuff 中
byteBuffer.put("你好".getBytes(StandardCharsets.UTF_8));
// 2. 或者通过Charset 的 encode方法获取 _byteBuff
ByteBuffer _byteBuffer = StandardCharsets.UTF_8.encode("你好");
ByteBuffer __byteBuffer = Charset.forName("UTF-8").encode("你好");
// 3. 通过 NIO的API wrap方法
ByteBuffer ___byteBuffer = ByteBuffer.wrap("你好".getBytes(StandardCharsets.UTF_8));
// 将 ByteBuffer 转为 String
String str = StandardCharsets.UTF_8.decode(__byteBuffer).toString();
  1. Scattering Reads
    分散读取,有一个文本文件 3parts.txt
onetwothree

使用如下方式读取,可以将数据填充至多个 buffer

try (FileChannel channel = new RandomAccessFile("3parts.txt", "r").getChannel()) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(3);
            ByteBuffer _byteBuffer = ByteBuffer.allocate(3);
            ByteBuffer __byteBuffer = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{byteBuffer, _byteBuffer, __byteBuffer});
            byteBuffer.flip();
            _byteBuffer.flip();
            __byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                System.out.print((char)byteBuffer.get());
            }
            System.out.println();
            while (_byteBuffer.hasRemaining()) {
                System.out.print((char)_byteBuffer.get());
            }
            System.out.println();
            while (__byteBuffer.hasRemaining()) {
                System.out.print((char)__byteBuffer.get());
            }
            System.out.println();
        } catch (IOException e) {
            System.out.println(e);
        }
  1. Gathering Writes
    集中写入,将三个bytebuffer 的 内容写入 words.txt
ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode("hello");
ByteBuffer _byteBuffer = StandardCharsets.UTF_8.encode("world");
// 6 bytes
ByteBuffer __byteBuffer = StandardCharsets.UTF_8.encode("你好");
try (FileChannel channel = new RandomAccessFile("word.txt", "rw").getChannel()) {
	// 将三个bytebuff 集中写入到 fileChannel中
    channel.write(new ByteBuffer[]{byteBuffer, _byteBuffer, __byteBuffer});
} catch (IOException e) {
    System.out.println(e);
}

思考:使用 分散读集中写,将减少数据在 ByteBuffer中的复制

  1. 粘包半包分析
    网络上有很多条数据发送给服务器,数据之间通过 \n 的格式进行分隔,但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有三条为
    Hello,world\n
    I’m zhangsan\n
    How are you?\n
    变成了下面的两个 ByteBuffer (粘包–发送一次发多条数据、半包–服务器的缓冲区大小决定的)
    Hello,world\nI’m zhangsan\nH
    ow are you?\n
    现在要求编写程序,将错乱的数据恢复成原始数据按照\n分隔的数据
 public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(32);
        byteBuffer.put("Hello,World\nI'm zhangsan\nHo".getBytes(StandardCharsets.UTF_8));
        split(byteBuffer);
        byteBuffer.put("w are you?\n".getBytes(StandardCharsets.UTF_8));
        split(byteBuffer);
    }

    private static void split(ByteBuffer byteBuffer) {
        // 切换成读模式
        byteBuffer.flip();
        // limit 上限
        for (int i = 0; i < byteBuffer.limit(); i++) {
            // 找到一条完整的消息
            if(byteBuffer.get(i) == '\n') {
                // 把完整的消息存储到 新的byteBuffer中
                int length = i + 1 - byteBuffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                // 从 byteBuffer中读,向 target 中写
                for (int i1 = 0; i1 < length; i1++) {
                    target.put(byteBuffer.get());
                }
                for (int j = 0; j < target.limit(); j++) {
                    System.out.print((char)target.get(j));
                }
            }
        }
        // 把未读的移到首部
        byteBuffer.compact();
    }
  1. 文件编程
    5.1 FileChannel

FileChannel 只能工作在阻塞模式下

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

  • 通过 FileInputStream 获取的 channel 只能读
  • 通过 FileOutputStream 获取的 channel 只能写
  • 通过 RandomAccessFile 是否读写由 RandomAccessFile的读写模式决定

读取
会从 channel 读取数据填充 ByteBuffer, 返回值表示读取到了多少字节,-1表示到达文件的末尾

int readBytes = channel.read(buffer);

写入
写入的正确姿势如下,

ByteBuffer buffer = ...;
buffer.put(...);	// 存入数据
buffer.flip();	// 切换读模式
while(buffer.hasRemaining()) {
	channel.write(buffer);
}

在while中调用channel.write是因为 write方法并不能保证一次将缓冲区的数据全部写入到channel中

关闭
channel必须关闭,不过调用了FileInputStream、FileOutputStream或者RandomAccessFile的close方法会简介地调用channel的close方法

位置
获取当前位置

long pos = channel.position();

设置当前位置

long newPos = ...;
channel.position(newPos);

设置当前位置时,如果设置为文件的末尾

  • 这时读取会返回 -1
  • 这时写入,会追加内容,但要注意如果positon超过了文件末尾,再写入时会在新内容和原来末尾之间存在空洞(00)

大小
FileChannel可以通过size方法获取文件大小

强制写入
操作系统出于性能考虑,会将数据缓存,不是立刻写入磁盘,可以调用force(true)方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘

5.2 两个Channel 传输数据

try (FileChannel from = new FileInputStream("data.txt").getChannel();
             FileChannel to = new FileOutputStream("data2.txt").getChannel()) {
             	// 从起始位置读到文件最后 效率高,底层会利用操作系统的零拷贝进行优化
             	// 传输文件有上限,最大2G
                from.transferTo(0, from.size(), to);
                // 如果需要 传输大于2G的数据
                // for(long left = from.size(); left > 0;) {
                //     left -= from.transferTo(from.size() - left, left, to);
                // }
        } catch (IOException e) {
        }

5.3 Files(对文件操作的)的 API
5.3.1 walkFileTree 通过 树形文件访问,提供操作,例如删除某个文件夹下的文件

Files.walkFileTree(Paths.get("D:/Program Files/Java/jdk-11.0.9"), new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println("===>" + dir);
                return super.preVisitDirectory(dir, attrs);
            }

            @Override
            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);
            }
        });

5.3.2 Files的walk 方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值