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

news2024/11/13 15:11:39

【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();
			//把这条完整消息存入新的 ByteBuffer
			ByteBuffer 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.txt

Path source = Paths.get("d:\\1.txt");	//绝对路径 代表了 d:\1.txt

Path source = Paths.get("d:/1.txt");	//绝对路径 代表了 d:\1.txt

Path 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>(){
		@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{
	
	//计数器,需要使用原子类,匿名类如果要使用外部变量,则该变量的地址值不能发生改变
	AtomicInteger jarCount = new AtomicInteger();

	Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVistor<Path>(){
		@Override
		public 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>(){
		@Override
		public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException{
			Files.delete(file)
			return super.visitFile(file, attrs);
		}

		@Override
		public 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.coloradmin.cn/o/391518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java SPI机制了解与应用

1. 了解SPI机制 我们在平时学习和工作中总是会听到Java SPI机制&#xff0c;特别是使用第三方框架的时候&#xff0c;那么什么是SP机制呢&#xff1f;SPI 全称 Service Provider Interface&#xff0c;是 Java 提供的一套用来被第三方实现或者扩展的接口&#xff0c;它可以用来…

【java】Java连接mysql数据库及mysql驱动jar包下载和使用

文章目录JDBCJDBC本质&#xff1a;JDBC作用&#xff1a;跟数据库建立连接发送 SQL 语句返回处理结果操作流程和具体的连接步骤如下&#xff1a;操作步骤&#xff1a;需要导入驱动jar包 mysql-connector-java-8.0.22.jar注册驱动获取数据库连接对象 Connection定义sql获取执行sq…

第十届CCF大数据与计算智能大赛总决赛暨颁奖典礼在苏州吴江顺利举办

2月24日-25日&#xff0c;中国计算机学会&#xff08;CCF&#xff09;主办、苏州市吴江区人民政府支持&#xff0c;苏州市吴江区工信局、吴江区东太湖度假区管理办公室、苏州市吴江区科技局、CCF大数据专家委员会、CCF自然语言处理专业委员会、CCF高性能计算专业委员会、CCF计算…

深度剖析C语言符号篇

致前行的人&#xff1a; 人生像攀登一座山&#xff0c;而找寻出路&#xff0c;却是一种学习的过程&#xff0c;我们应当在这过程中&#xff0c;学习稳定冷静&#xff0c;学习如何从慌乱中找到生机。 目录 1.注释符号&#xff1a; 2.续接符和转义符&#xff1a; 3.回车与换行…

独立产品灵感周刊 DecoHack #050 - 基于 ChatGPT 的 API 能做什么产品

ChatGPT 的 API 3.5 开放过去了一周&#xff0c;果然和预想的一样&#xff0c;接入 API 的产品爆炸式地出现了非常多&#xff0c;很多产品已经做的非常好用了&#xff0c;在很多场景方便了太多。本期整理了一些做的很不错的产品。 本周刊记录有趣好玩的独立产品设计开发相关内容…

一款好的风险管理软件可以做什么

风险管理软件哪个好&#xff1f;使用Zoho Projects易于使用的项目风险管理软件&#xff0c;最大限度地减少收入损失并快速调整您的投资组合&#xff0c;保护您的项目投资。Zoho Projects的高级风险管理软件可在您最需要的时候安全的保护您的业务。使用Zoho Projects强大的风险管…

【springmvc】视图

SpringMVC的视图 SpringMVC中的视图是View接口&#xff0c;视图的作用渲染数据&#xff0c;将模型Model中的数据展示给用户 SpringMVC视图的种类很多&#xff0c;默认有转发视图和重定向视图 当工程引入jstl的依赖&#xff0c;转发视图会自动转换为JstlView 若使用的视图技…

内核性能评估测试及具体修改操作步骤记录

步骤记录前言一、查看环境配置二、LRU缓存空间调整三、进程扫描时间间隔四、与其他内核对比的工作负载测试&#xff08;另一个内核的编译&#xff09;总结前言 记录的相关操作有&#xff1a;查看服务器硬件环境、LRU缓存大小修改、内核命名、内核编译以及进程执行周期的设置。…

操作系统——17.进程同步与进程互斥

这篇文章我们来讲一下进程同步与进程互斥的问题 目录 1.概述 2.什么是进程同步 3.什么是进程互斥 4.小结 1.概述 首先&#xff0c;我们来看一下本篇内容的大体框架&#xff1a; 2.什么是进程同步 知识点回顾:进程具有异步性的特征。 异步性是指&#xff0c;各并发执行的…

vue里使用driver.js实现项目功能向导指引

介绍 https://github.com/kamranahmedse/driver.js driver.js 是一个轻量级、无依赖的原生JavaScript引擎&#xff0c;在整个页面中驱动用户的注意力&#xff0c;强大的、高度可定制的原生JavaScript引擎&#xff0c;无外部依赖&#xff0c;支持所有主流浏览器。 安装 npm …

CHAPTER 1 Linux Filesystem Management

Linux Filesystem Management1 文件系统是什么2 文件系统的组成3 inode详解1. inode到底是什么2. inode的内容3. inode的大小4. inode的号码5. 硬链接6. 软链接4 存储区域5 常见文件系统的类型1. 根文件系统2. 虚拟文件系统3. 真文件系统4. 伪文件系统5. 网络文件系统1 文件系统…

2023年网络安全最应该看的书籍,弯道超车,拒绝看烂书

学习的方法有很多种&#xff0c;看书就是一种不错的方法&#xff0c;但为什么总有人说&#xff1a;“看书是学不会技术的”。 其实就是书籍没选对&#xff0c;看的书不好&#xff0c;你学不下去是很正常的。 一本好书其实不亚于一套好的视频教程&#xff0c;尤其是经典的好书…

Java 面试常见项目问题回答

之前整理了好几期&#xff0c;我面试时遇到的面试候选人&#xff0c;我是如何我去筛选的&#xff0c;这一期&#xff0c;我们来看下一些 面试常问的业务性的问题 你们公司权限认证是如何实现的? 这其实是个通用性的问题&#xff0c;大部分公司 小型公司&#xff0c;或者中型公…

Praat之基频分析

Praat之基频分析 测量基频F0的方法 自相关 Autocorrelation&#xff08;易出现pitch-halving\pitch-double&#xff09;窄带谱图 Narrowband spectrogram&#xff08;第一谐波就是基频&#xff09;倒谱分析 Cepstral analysis测量声门波 glottal pluse&#xff08;通过波形&a…

拥抱数字时代 | 第十六届中国大数据技术大会成功举办

2月26日&#xff0c;由中国计算机学会&#xff08;CCF&#xff09;主办&#xff0c;苏州市吴江区人民政府支持&#xff0c;CCF大数据专家委员会、苏州市吴江区工信局、吴江区东太湖度假区管理办公室、苏州市吴江区科技局、苏州大学未来科学与工程学院、DataFountain数联众创承办…

史上最全最详细的Java架构师成长路径图,程序员必备

从新手码农到高级架构师&#xff0c;要经过几步&#xff1f;要多努力&#xff0c;才能成为为人倚重的技术专家&#xff1f;本文将为你带来一张程序员发展路径图&#xff0c;但你需要知道的是&#xff0c;天下没有普适的道理&#xff0c;具体问题还需具体分析&#xff0c;实践才…

JavaScript基础三、数据类型

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

nest.js学习笔记(一)

nest.js学习笔记&#xff08;一&#xff09;一、安装nest.js前置条件运行项目目录介绍二、nest.js cli 常用命令三、RESTful 风格设计1.接口url2.RESTful 版本控制四、获取前端传过来的参数1.获取Get请求参数2.获取Post请求参数3.动态路由4.获取Header信息5.自定义状态码nest.j…

Kotlin lateinit 和 lazy 之间的区别 (翻译)

Kotlin 中的属性是使用var或val关键字声明的。Late init 和 lazy 都是用来初始化以后要用到的属性。 由于这两个关键字都用于声明稍后将要使用的属性&#xff0c;因此让我们看一下它们以及它们的区别。 Late Init 在下面的示例中&#xff0c;我们有一个变量 myClass&#xff0…

【前缀和】截断数组、K倍区间、激光炸弹

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…