【Netty】第一章 NIO 三大组件、ByteBuffer 和文件编程

news/2024/11/28 9:34:56/

【Netty】第一章 NIO 三大组件、ByteBuffer 和文件编程

文章目录

  • 【Netty】第一章 NIO 三大组件、ByteBuffer 和文件编程
  • 一、Channel & Buffer
  • 二、Selector
  • 三、ByteBuffer
    • 1.ByteBuffer 使用方式
    • 2.ByteBuffer 结构
    • 3.ByteBuffer 常用方法
    • 4.Scattering Reads
    • 4.Gathering Write
    • 5.黏包半包
  • 四、文件编程
    • 1.FileChannel
    • 2.两个 Channel 传输数据
    • 3.Path
    • 4.Files

一、Channel & Buffer

channel 类似于 stream,它是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层

常见的 Channel 有:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

常见的 buffer 有:

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

二、Selector

多线程
此前,服务器每接收到一个客户端的请求就需要开启一个线程建立 socket 连接,这样会有很多弊端:

  • 内存占用高(Windows 下默认一个线程占用 1MB)
  • 线程上下文切换成本高
  • 只适合连接数少的场景

线程池
固定线程池,线程数量是固定的,当请求太多时,那么这些请求会被阻塞,直到有 socket 断开线程空闲下来

  • 阻塞模式下,线程仅能处理一个 socket 连接
  • 仅适合短连接场景

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

调用 selector 的 select() 方法会阻塞知道 channel 发生了读写就绪事件,这些事件发生,select() 方法就会返回这些事件交给 thread 来处理

三、ByteBuffer

1.ByteBuffer 使用方式

  1. 向 buffer 写入数据,例如调用 channel.read(buffer)
  2. 调用 flip() 切换至读模式
  3. 从 buffer 读取数据,例如调用 buffer.get()
  4. 调用 clear() 或 compact() 切换至写模式
  5. 重复 1~4 步骤

2.ByteBuffer 结构

ByteBuffer 有以下重要属性:

  • capacity
  • position
  • limit

初始状态下
在这里插入图片描述
写模式下
在这里插入图片描述
调用 flip() 后,position 切换为读取位置,limit 切换为为读取限制
在这里插入图片描述

3.ByteBuffer 常用方法

分配空间
可以使用 allocate() 方法为 ByteBuffer 分配空间,其他 buffer 类也有该方法

ByteBuffer buf = ByteBuffer.allocate(16);

向 buffer 写入数据
有两种方法

  • 调用 channel 的 read 方法
  • 调用 buffer 自己的 put 方法
int readBytes = channel.read(buf);

buf.put((byte)127);

从 buffer 读取数据
同样有两种方法

  • 调用 channel 的 write 方法
  • 调用 buffer 自己的 get 方法
int writeBytes = channel.write(buf);

byte b = buf.get();

get 方法会让 position 读指针向后移动,如果想重复读取数据

  • 可以调用 rewind() 方法将 position 重新置为 0
  • 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动指针

4.Scattering Reads

分散读取,有一个文本文件 3parts.txt

onetwothree

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

try(RandomAccessFile file = new RandomAccessFile("helloworld/3parts.txt", "rw")){FileChannel channel = file.getChannel();ByteBuffer a = ByteBuffer.allocate(3);ByteBuffer b = ByteBuffer.allocate(3);ByteBuffer c = ByteBuffer.allocate(5);channel.read(new ByteBuffer[]{a, b, c});
}catch(IOException e){e.printStackTrace();
}

4.Gathering Write

集中写入

ByteBuffer b1 = StandardCharasets.UTF_8.encode("hello");
ByteBuffer b2 = StandardCharasets.UTF_8.encode("world");
ByteBuffer b3 = StandardCharasets.UTF_8.encode("你好");try(FileChannel channel = new RandomAccessFile("words2.txt", "rw")){channel.write(new ByteBuffer[]{b1, b2, b3})
}catch(IOException e){
}

5.黏包半包

网络上有多条数据发送给服务器,数据之间使用 \n 进行分隔
但由于某种原因这些数据在接收时被重新组合,例如原始数据有3条:

Hello,world\n
I'm zhangsan\n
How are you?\n

变成了下面的两个 byteBuffer

Hello,world\nI'm zhangsan\nHo
w are you?\n

编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据

ByteBuffer source = ByteBuffer,allocate(32);
source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\n".getBytes());
split(source);private static void split(ByteBuffer source){source.flip();for(int i = 0; i < source.limit(); i++){//找到一条完整消息if(source.get(i) == '\n'){int length = i + 1 - source.position();//把这条完整消息存入新的 ByteBufferByteBuffer target = ByteBuffer.allocate(256);//从 source 读,向 target 写for(int j = 0; j < length; j++){target.put(source.get());}debugAll(target);}}
source.compact();
}

四、文件编程

1.FileChannel

FileChannel 只能工作在阻塞模式下

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

  • 通过 FileInputStream 获取的 channel 只能读
  • 通过 FileOutputStream 获取的 channel 只能写
  • 通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定

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

int readBytes = channel.read(buffer);

写入
写入的正确方式如下,SocketChannel

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

在 while 中调用 channel.write 是因为 write 方法并不能保证一次性将 buffer 中的内容全部写入 channel

关闭
channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close() 方法会间接地调用 channel 的 close 方法

位置
获取当前位置

long pos = channel.position();

设置当前位置

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

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

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

大小
使用 size 方法获取文件的大小

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

2.两个 Channel 传输数据

String FROM = "helloworld/data.txt";
String TO = "helloworld/to.txt";
long start = System.nanoTime();
try(FileChannel from = new FileInputStream(FROM).getChannel();FileChannel to = new FileOutputStream(TO).getChannel();){//效率高,底层会利用操作系统的零拷贝进行优化from.transferTo(0, from.size(), to);
}catch(IOException){e.printStackTrace();
}
long end = System.nanaTime();
System.out.println("transferTo 用时:" + (end - start) / 1000_000.0);

输出

transferTo 用时:8.2001

3.Path

jdk7 引入了 Path 和 Paths 类

  • Path 用来表示文件路径
  • Paths 是工具类,用来获取 Path 实例
Path source = Paths.get("1.txt");		//相对路径 使用 user.dir 环境变量来定位 1.txtPath source = Paths.get("d:\\1.txt");	//绝对路径 代表了 d:\1.txtPath source = Paths.get("d:/1.txt");	//绝对路径 代表了 d:\1.txtPath projects = Paths.get("d\\data", "projects");		//代表了 d:\data\projects

4.Files

检查文件是否存在

Path path = Paths.get("helloworld/data.txt");
System.out.println(Files.exists(path));

创建一级目录

Path path = Paths.get("helloworld/d1");
Files.createDirectory(path);
  • 如果目录已存在,会抛异常 FileAlreadyExistsException
  • 不能一次创建多级目录,否则会抛异常 NoSuchFileException

创建多级目录

Path path = Path.get("helloworld/d1/d2");
Files.createDirectories(path);

拷贝文件

Path source = Paths.get("helloworld/data.txt");
Path target = Paths.get("helloworld/target.txt");Files.copy(source, target);
  • 如果文件已存在,会抛异常 FileAlreadyExistsException

如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来空值

Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

移动文件

Path source = Paths.get("helloworld/data.txt");
Path target = Paths.get("helloworld/data.txt");Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
  • StandardCopyOption.ATOMIC_MOVE保证文件移动的完整性

删除文件

Path target = Paths.get("helloworld/target.txt");
Files.delete(target);
  • 如果文件不存在,会抛异常 NoSuchFileException

删除目录

Path target = Paths.get("hellowworld/d1");
Files.delete(target);
  • 如果目录还有内容,会抛异常 DirectoryNoEmptyException

遍历目录文件

public static void main(String[] args) throws IOException{//计数器,需要使用原子类,匿名类如果要使用外部变量,则该变量的地址值不能发生改变AtomicInteger dirCount = new AtomicInteger();AtomicInteger fileCount = new AtomicInteger();Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor<Path>(){@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException{System.out.println("====="+dir);dirCount.incrementAndGet();return super.preVisitDirectory(dir, attrs);}@Overridepublic 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{//计数器,需要使用原子类,匿名类如果要使用外部变量,则该变量的地址值不能发生改变AtomicInteger jarCount = new AtomicInteger();Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor<Path>(){@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{if(file.toString().endWith(".jar")){System.out.println(file);}return super.visitFile(file, attrs);}});System.out.println("jar count:" + jarCount);
}

删除多级目录
(友情提示:不要直接执行!删掉后不会进回收站!)

public static void main(String[] args) throws IOException{Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor<Path>(){@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{Files.delete(file)return super.visitFile(file, attrs);}@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException{Files.delete(dir);return super.postVisitDirectory(file, exc);}});
}

拷贝多级目录

public static void main(String[] args) throws IOException{String source = "D:\\sourceDir";String target = "D:\\targetDir";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();}});
}

http://www.ppmy.cn/news/30438.html

相关文章

ChatGPT  一本正经的胡说八道 那也看看原理吧

最近&#xff0c;ChatGPT横空出世。这款被马斯克形容为“强大到危险”的AI&#xff0c;不但能够与人聊天互动&#xff0c;还能写文章、改代码。于是&#xff0c;人们纷纷想让AI替自己做些什么&#xff0c;有人通过两分钟的提问便得到了一篇完美的论文&#xff0c;有人希望它能帮…

C#开发的OpenRA的游戏主界面怎么样创建3

继续游戏主界面创建的主题, 我们知道游戏的主界面上有很多部件,比如显示文本的标签(LabelWidget), 显示按钮(ButtonWidget)。那么这些部件又是如何创建在主界面上的呢? 其实这些部件是否显示,都是来源于文件yaml,在这里就是文件mainmenu.yaml, 在这个文件里定义了所有…

28个案例问题分析---027---单表的11个Update接口--MyBatis

一&#xff1a;背景介绍 项目开发中。我们使用的是MyBatis&#xff0c;在MyBatis的xml文件里&#xff0c;两个表的更新功能&#xff0c;写了足足11个更新接口&#xff0c;毫无复用的思想 这种方式可以正常的实现功能&#xff0c;但是没有复用&#xff0c;无论是从时间上还是维…

Java Web 实战 03 - 多线程基础(2)

Java Web 实战 03 - 多线程基础篇 2二 . Thread类常见方法2.1 Thread 的常见构造方法2.2 Thread 的几个常见属性getId()getName()getState()getPriority()isDaemon()案例 : 实现 getId()、getName()、 getState()、getPriority()、isDaemon()、isAlive()2.3 启动一个线程-start…

Qt音视频开发20-vlc内核动态保存录像文件(不需要重新编译源码)

一、前言 在vlc默认提供的保存文件方式中&#xff0c;通过打开的时候传入指定的参数来保存文件&#xff0c;直到关闭播放生成文件&#xff0c;这种方式简单暴力&#xff0c;但是不适用大部分的场景&#xff0c;大部分时候需要的是提供开始录制和停止录制的功能&#xff0c;也就…

JavaScript RegExp 正则对象

文章目录JavaScript RegExp 正则对象RegExp 对象修饰符test()exec()方括号元字符量词RegExp 对象方法支持正则表达式的 String 对象的方法JavaScript RegExp 正则对象 RegExp&#xff1a;是正则表达式&#xff08;regular expression&#xff09;的简写。 RegExp 对象 正则表…

【精品】SpringBoot中基于拦截器实现登录验证功能

拦截器简介 拦截器是属于springmvc体系的&#xff0c;只能拦截controller的请求。拦截器&#xff08;Interceptor&#xff09;是一种动态拦截方法调用的机制&#xff0c;在SpringMVC中动态拦截控制器方法的执行。 Interceptor 作用 日志记录&#xff1a;记录请求信息的日志&…

浏览器主页被hao123劫持的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…