【网络编程系列】NIO

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。

img

非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝

✨✨ 欢迎订阅本专栏 ✨✨

博客目录

      • 1.基础
        • 1.IO 发展历程
        • 2.I/O 请求
        • 3.BIO
        • 4.NIO
        • 5.IO 多路复用
        • 6.信号驱动
        • 7.异步 IO
        • 8.NIO 三大组件
        • 9.事件
        • 10.阻塞和非阻塞
        • 11.Java 中有几种类型的流
        • 12.字节流如何转为字符流
        • 13.字节流和字符流的区别
        • 14.序列化和 IO 的关系
        • 15.BIO 和 NIO 的概念
        • 16.异常机制的过程
        • 17.异常体系
        • 18.异常输出打印的常用方法
        • 19.Throw 和 Throws 的区别
        • 20.try-with-resources 替代 try-catch-finally
        • 21.读文件行
      • 2.ByteBuffer
        • 1.ByteBuffer 结构
        • 2.ByteBuffer 实现
        • 3.分配空间
        • 4.向 buffer 写入数据
        • 5.从 buffer 读取数据
        • 6.mark 和 reset
        • 7.字符串与 ByteBuffer 互裝
        • 8.Buffer 相关优化
        • 9.处理消息边界
        • 10.ByteBuffer 大小分配
        • 11.ByteBuffer 正确使用姿势
      • 3.channel
        • 1.stream 和 channel
      • 4.selector
        • 1.监听 Channel 事件
        • 2.selector 和 selectedKeys
        • 3.select 何时不阻塞
        • 4.如何拿到 cpu 个数
        • 5.利用多线程优化
        • 6.wakeup
      • 5.文件编程
        • 1.FileChannel
        • 2.获取
        • 3.读取
        • 4.写入
        • 5.位置
        • 6.大小
        • 7.Path
        • 8.Files
        • 9.拷贝文件
        • 10.移动文件
        • 11.删除文件
        • 12.删除目录
      • 6.IO 模型
        • 1.同步异步
        • 2.IO 模型
        • 3.阻塞 IO
        • 4.非阻塞 IO
        • 5.多路复用
        • 6.异步 IO
      • 7.零拷贝
        • 1.传统 IO
        • 2.NIO 优化
        • 3.sendFile 优化
        • 4.进一步优化

1.基础

1.IO 发展历程

  • 在 JDK1.4 投入使用之前,只有 BIO 一种模式
  • JDK1.4 以后开始引入了 NIO 技术,支持 select 和 poll
  • JDK1.5 支持了 epoll
  • JDK1.7 发布了 NIO2,支持 AIO 模型

2.I/O 请求

I/O 调用阶段:用户进程向内核发起系统调用
I/O 执行阶段:内核等待 I/O 请求处理完成返回

3.BIO

image-20230417171509357

4.NIO

image-20230417171544016

5.IO 多路复用

image-20230417171601191

6.信号驱动

image-20230417171626395

7.异步 IO

image-20230417171644758

8.NIO 三大组件

NIO 是 non-blocking IO 非阻塞 IO

会详细讲解 NIO 的 Selector、ByteBuffer 和 Channel 三大组件。

Channel

channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将

buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层

常见的 Channel 有

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

buffer

buffer 则用来缓冲读写数据,常见的 buffer 有

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

Selector

selector 单从字面意思不好理解,需要结合服务器的设计演化来理解已的用途

服务器设计

多线程版本

image-20230419141948449

多袋程版缺点

  • 内存占用高
  • 线程上下文切换成本高
  • 只适合连接数少的场景

线程池版本

image-20230419142017666

线程池版缺点

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

selector 版设计

image-20230419142113241

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

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

9.事件

  • accept -会在有连接请求时触发

  • connect -是客户端,连接建立后触发

  • read - 可读事件

  • wite-可写事件

image-20230426155710244

10.阻塞和非阻塞

阻塞

  • 阻塞模式下,相关方法都会导致线程暂停

    • ServerSocketChannel.accept 会在没有连接建立时让线程暂停
    • SocketChannel.read 会在没有数据可读时让线程暂停
    • 阻塞的表现其实就是线程暂停了,暂停期间不会占用 Cpu,但线程相当于闲置
  • 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持

  • 但多线程下,有新的问题,体现在以下方面

    • 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 00M,并且线程太多,反而会因为频繁上下文切换导致性能降低

    • 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接

非阻塞

  • 在某个 Channel 没有可读事件时,线程不必阻塞,它可以去处理其它有可读事件的 Channel
  • 数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)
  • 写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去

11.Java 中有几种类型的流

  • 按照流的流向分:输入流(inputStream)和输出流(outputStream)
  • 按照操作单元划分:字节流和字符流
  • 按照流的角色功能划分:节点流和处理流。
    • 节点流:可以从或向一个特定的地方(节点)读写数据。如 FileReader
    • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

java-javase-basis-008

java-javase-basis-009

12.字节流如何转为字符流

字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。

字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象.

13.字节流和字符流的区别

字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符字符数组或字符串,而字节流处理单元为 1 个字节, 操作字节和字节数组。所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点

所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。

字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串

字节流提供了处理任何类型的 IO 操作的功能,但它不能直接处理 Unicode 字符,而字符流就可以。

14.序列化和 IO 的关系

[什么是 Java 序列化,如何实现 java 序列化](https://www.cnblogs.com/yangchunze/p/6728086.html)

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序 列 化 的 实 现 : 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 , 该 接 口 没 有 需 要 实 现 的 方 法 ,

implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

实体类

public class Customer implements Serializable   {private String name;private int age;public Customer(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "name=" + name + ", age=" + age;}
}

测试类

public class Test {public static void main(String[] args) throws Exception {/*其中的  D:\\objectFile.obj 表示存放序列化对象的文件*/// 序列化对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\\objectFile.obj"));Customer customer = new Customer("BWH_Steven", 22);out.writeObject("Hello!");    // 写入字面值常量out.writeObject(new Date());  // 写入匿名Date对象out.writeObject(customer);    // 写入customer对象out.close();// 反序列化对象ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\objectFile.obj"));System.out.println("obj1 " + (String) in.readObject()); // 读取字面值常量System.out.println("obj2 " + (Date) in.readObject());   // 读取匿名Date对象Customer obj3 = (Customer) in.readObject();    			// 读取customer对象System.out.println("obj3 " + obj3);in.close();}}

运行结果

// 实体类实现 Serializable 接口
obj1 Hello!
obj2 Sat Feb 06 11:17:57 CST 2021
obj3 name=BWH_Steven, age=22// 实体类不实现 Serializable 接口
Exception in thread "main" java.io.NotSerializableException: cn.ideal.pojo.Customerat java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)at cn.ideal.Test.main(Test.java:27)

15.BIO 和 NIO 的概念

[关于 BIO 和 NIO 的理解](https://www.cnblogs.com/zedosu/p/6666984.html)

16.异常机制的过程

异常就是在程序发生异常时,强制终止程序运行,并且将异常信息返回,由开发者决定是否处理异常

简单说一下这个异常机制的过程:

当程序无法运行后,它会从当前环境中跳出,并且抛出异常,之后,它会先 new 一个异常对象,然后在异常位置终止程序,并且将异常对象的引用从当前环境中返回,这时候异常处理机制接管程序,并且开始寻找可以继续执行程序的恰当位置。

17.异常体系

  1. Error —— 错误:程序无法处理的严重错误,我们不作处理
    • 这种错误一般来说与操作者无关,并且开发者与应用程序没有能力去解决这一问题,通常情况下,JVM 会做出终止线程的动作
  2. Exception —— 异常:异常可以分为运行时异常和编译期异常
  • RuntimeException:即运行时异常,我们必须修正代码

    • 这些异常通常是由于一些逻辑错误产生的这类异常在代码编写的时候不会被编译器所检测出来,是可以不需要被捕获,但是程序员也可以根据需要行捕获抛出,(不受检查异常)这类异常通常是可以被程序员避免的。

    • 常见的 RUNtimeException 有:NullpointException(空指针异常),ClassCastException(类型转 换异常),IndexOutOfBoundsException(数组越界异常)等。

  • 非 RuntimeException编译期异常,必须处理,否则程序编译无法通过

  • 这类异常在编译时编译器会提示需要捕获,如果不进行捕获则编译错误。

  • 常见编译异常有:IOException(流传输异常),SQLException(数据库操作异常)等。

java-javase-basis-010

18.异常输出打印的常用方法

方法方法说明
public String getMessage()回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了
public Throwable getCause()返回一个 Throwable 对象代表异常原因
public String toString()使用 getMessage()的结果返回类的串级名字
public void printStackTrace()打印 toString()结果和栈层次到 System.err,即错误输出流

示例:

public class Demo {public static void main(String[] args) {int a = 520;int b = 0;int c;try {System.out.println("这是一个被除数为0的式子");c = a / b;} catch (ArithmeticException e) {System.out.println("除数不能为0");}}
}//运行结果
这是一个被除数为0的式子
除数不能为0

我们用上面的例子给出异常方法的测试

// System.out.println(e.getMessage()); 结果如下:
/ by zero
// System.out.println(e.getCause()); 结果如下:
null
// System.out.println(e.toString()); 结果如下:
java.lang.ArithmeticException: / by zero
// e.printStackTrace(); 结果如下:
java.lang.ArithmeticException: / by zeroat cn.bwh_01_Throwable.Demo.main(Demo.java:10)

19.Throw 和 Throws 的区别

Throw

  • 作用在方法内,表示抛出具体异常,由方法体内的语句处理。

  • 具体向外抛出的动作,所以它抛出的是一个异常实体类。若执行了 Throw 一定是抛出了某种异常。

Throws

  • 作用在方法的声明上,表示如果抛出异常,则由该方法的调用者来进行异常处理。

  • 主要的声明这个方法会抛出会抛出某种类型的异常,让它的使用者知道捕获异常的类型。

  • 出现异常是一种可能性,但不一定会发生异常。

20.try-with-resources 替代 try-catch-finally

面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是 try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources 语句让我们更容易编写必须要关闭的资源的代码,若采用 try-finally 则几乎做不到这点。—— Effecitve Java

Java 从 JDK1.7 开始引入了 try-with-resources ,在其中定义的变量只要实现了 AutoCloseable 接口,这样在系统可以自动调用它们的 close 方法,从而替代了 finally 中关闭资源的功能。

使用 try-catch-finally 你可能会这样做

try {// 假设这里是一组关于 文件 IO 操作的代码
} catch (IOException e) {e.printStackTrace();
} finally {if (s != null) {s.close();}
}

但现在你可以这样做

try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("test.txt")))) {// 假设这里是操作代码
} catch (IOException e) {e.printStackTrace();
}

如果有多个资源需要 close ,只需要在 try 中,通过分号间隔开即可

try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("test.txt")));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("test.txt")))) {// 假设这里是操作代码
} catch (IOException e) {e.printStackTrace();
}

21.读文件行

List<String> lines = Files.readAllLines(Paths.get(dicPath));

2.ByteBuffer

1.ByteBuffer 结构

ByteBuffer 有以下重要属性

  • capacity
  • position
  • limit

image-20230419144602748

写模式下,position 是写入位置,limit 等于可写容量,capacity 是最大容量

flip 动作发生后,position 切换为读取位置,limit 切换为读取限制

image-20230419144727733

clear 动作发生后,状态

image-20230419144739599

compact 方法,是把未读完的部分向前压缩,然后切换至写模式

image-20230419144800460

下面是一个简单的图形化展示,展示了一个 ByteBuffer 对象的 positionlimitcapacity 属性的关系:

+-------------------+------------------+------------------+
|      capacity     |       limit      |     position     |
+-------------------+------------------+------------------+
|                   |                  |                  |
|                   |                  |                  |
|                   |<---- remaining --->|<---- remaining --->|
|                   |                  |                  |
|                   |                  |                  |
|                   |<--------- buffer --------->|         |
|                   |                  |                  |
|                   |                  |                  |
|                   |<---- flip() ------>|<---- flip() ------>|
|                   |                  |                  |
|                   |                  |                  |
|                   |<------ clear() ------->|         |
|                   |                  |                  |
+-------------------+------------------+------------------+

上面的图形展示了一个 ByteBuffer 对象,它具有以下属性:

  • capacity:表示 ByteBuffer 对象的容量,即它可以包含多少字节。
  • limit:表示 ByteBuffer 对象中可读取或可写入的字节数。它始终小于或等于 capacity 属性。
  • position:表示当前读取或写入操作所处的位置。初始时,position 的值为 0,它始终小于或等于 limit 属性。

在创建一个 ByteBuffer 对象后,可以使用 put() 方法向其中写入数据。当写入完成后,需要调用 flip() 方法将 limit 属性设置为当前 position 的值,并将 position 的值重置为 0,以便读取数据。读取完成后,可以调用 clear() 方法将 limitposition 的值重置为初始值,以便重新写入数据。

需要注意的是,当读取或写入数据时,position 属性的值会随着操作的进行而自动增加。因此,在读取或写入数据之前,需要记录当前 position 的值,以便在操作完成后将 position 的值恢复到原始值。

2.ByteBuffer 实现

class java.nio.HeapByteBuffer:java 堆内存,读写效率较低,受到 GC 的影响
class java.nio.DirectByteBuffer:直接内存,读写效率高(少一次拷贝),不会受 GC,分配的效率低

3.分配空间

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

ByteBuffer.allocate(16);

4.向 buffer 写入数据

有两种办法

  • 调用 channel 的 read 方法
  • 调用 buffer 自己的 put 方法
#方式一
buffer.put(new byte[]{0x62, 0x63, 0x64});#方式二
channel.read(buffer);

5.从 buffer 读取数据

同样有两种办法

  • 调用 channel 的 write 方法
  • 调用 buffer 自己的 get 方法
#方式一
int writeBytes = channel.write(buf);#方式二
byte b =buf.get();

set 方法会让 position 读指针向后走,如果想重复读取数据

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

get(i)不会改交读索引的位置
System.out.println((char) buffer.get(3));

6.mark 和 reset

mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置

mark 微一个标记,记柔 position 位置,reset 是将 position 重置到 mark 的位置

7.字符串与 ByteBuffer 互裝

ByteBuffer bufferl = Standar dCharsets.UTF_8. encode("你好");
Bytesuffer buffer2 = Charset. forName("utf-8")encode("休好"):
debug (buffer1);
debug (buffer2);
CharBuffer buffer3 = Standardcharsets. UTF_8. decode (buffer 1);
System.out.print]n (buffer3.getClass ());
System.out.print]n(buffer3.toString());

8.Buffer 相关优化

  • ChannelBuffer 变更为 ByteBuf,Buffer 相关的工具类可以独立使用
  • Buffer 统一为动态变化,更安全地更改 Buffer 的容量
  • 增加新的数据类型 CompositeByteBuf,用于减少数据拷贝
  • GC 更加友好,增加池化缓存,4.1 版本开始 jemalloc 成为默认内存分配方式
  • 内存泄漏检测功能

9.处理消息边界

image-20230419184751915

  • 一种思路是固定消息长度,数据包大小一样,服务器按预定长度读取,缺点是浪费带宽
  • 另一种思路是按分隔符拆分,缺点是效率低
  • TLV 格式,即 Type 类型、Length 长度、Value 数据,类型和长度已知的情况下,就可以方便获取消息大小,分配合适的 buffer,缺点是 buffer 需要提前分配,如果内容过大,则影响 server 吞吐量
    • Http1.1 是 TLV 格式
    • Http2.0 是 LTV 格式

10.ByteBuffer 大小分配

  • 每个 channel 都需要记录可能被切分的消息,因为 ByteBuffer 不是线程安全的,因此需要为每个 channel 维

    护一个独立的 ByteBuffer

  • ByteBuffer 不能太大,比如一个 ByteBuffer 1Mb 的话,要支持百万连接就要 1Tb 内存,因此需要设计大小

    可变的 ByteBuffer

解决方案

  • 一种思路是首先分配一个较小的 buffer,例如 4k,如果发现数据不够,再分配 8k 的 buffer,将 4k

    buffer 内容拷贝至 8k buffer,优点是消息连续容易处理,缺点是数据拷贝耗费性能

  • 另一种思路是用多个数组组成 buffer,一个数组不够,把多出来的内容写入新的数组,与前面的区别是消

    息存储不连续解析复杂,优点是避免了拷贝引起的性能损耗

11.ByteBuffer 正确使用姿势

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

3.channel

1.stream 和 channel

  • stream 不会自动缓冲数据,channe 会利用系统提供的发送缓冲区、接收缓冲区(更为底层)
  • stream 仅支持阻塞 APl,channel 同时支持阻塞、非阻塞 APl,网络 channel 可配合 selector 实现多路复用
  • 二者均为全双工,即读写可以同时进行

4.selector

1.监听 Channel 事件

可以通过下面三种方法来监听是否有事件发生,方法的返回值代表有多少 channel 发生了事件

//方法1,阻塞直到绑定事件发生
int count = selector.select(O);//方法2,阻塞直到绑定事件发生,或是超时(时间单位为ms)
int count = selector.select(long timeout);//方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件
int countT= selector.selectNow();

2.selector 和 selectedKeys

image-20230418214149787

3.select 何时不阻塞

  • 事件发生时
    • 客户端发起连接请求,会触发 accept 事件
    • 客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件
    • channel 可写,会触发 write 事件
    • 在 linux 下 nio bug 发生时
  • 调用 selector.wakeup()
  • 调用 selector.close()
  • selector 所在线程 interrupt

4.如何拿到 cpu 个数

  • Runtime.getRuntime().availableProcessors()如果工作在 docker 容器下,因为容器不是物理隔离的,

    会拿到物理 cpu 个数,而不是容器申请时的个数

  • 这个问题直到 jdk 10 才修复,使用 jvm 参数 UseContainerSupport 配置,默认开启

5.利用多线程优化

现在都是多核 cpu,设计时要充分考虑别让 cpu 的力量被白白浪费,前面的代码只有一个选择器,没有充分利用多核 cpu,如何改进呢?

分两组选择器:

  • 单线程配一个选择器,专门处理 accept 事件
  • 创建 cpu 核心数的线程,每个线程配一个选择器,轮流处理 read 事件

image-20230421150405084

6.wakeup

selector.wakeup()//唤醒 select 方洗 boss
selector.select()//worker-0 阻塞
sc.register(selector, SelectionKey.OP_READ, null); // boss

因为 wakeup 方法的特性,即使提前唤醒,也不会在 select 方法阻塞

5.文件编程

1.FileChannel

FileChannel 只能工作在阻塞模式下

2.获取

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

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

3.读取

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

int readBytes = channel.read(buffer);

4.写入

写入的正确姿势如下

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

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

5.位置

//获取当前位置
long pos = channel.position();//设置当前位置
long newPos =.....;
channel.position(newPos);

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

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

6.大小

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

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

7.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
  • .代表了当前路径
  • …代表了上一级路径

例如目录结构如下
d:
|- data
I- projects
|-a
|-b

Path path = Paths.get ("d:\\data\ \projects\la\\.. \\b");
System.out.println(path);
system.out.println(path.normalize();//正常化路径//输出结果
d:\data\projects\a\.. \b
d: data projects\b

8.Files

检查文件是否存在

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

创建一级目录

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

创建多级目录用

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

9.拷贝文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/target.txt");
Files.copy(source, target);
  • 如果文件已存在,会抛异常 FileAlreadyExistsException
  • 如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

10.移动文件

Path source = Paths.get("helloword/data.txt");
Path target = Paths.get("helloword/data.txt");
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);

StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性

11.删除文件

Path target = Paths.get("helloword/target.txt");
Files.delete(target);

如果文件不存在,会抛异常 NoSuchFileException

12.删除目录

Path target = Paths.get("helloword/d1");
Files.delete(target);

如果目录还有内容,会抛异常 DirectoryNotEmptyException

6.IO 模型

1.同步异步

  • 同步阻塞
  • 同步非阻塞
  • 多路复用
  • 异步阻塞:不存在的类型
  • 异步非阻塞

同步异步:

  • 同步:线程自己去获取结果(一个线程)
  • 异步:线程自己不去获取结果,而是由其它线程送结果(至少两个线程)

2.IO 模型

  • 阻塞 IO
  • 非阻塞 IO
  • 多路复用
  • 信号驱动
  • 异步 IO

当调用一次 channel.read 或 stream.read 后,会切换至操作系统内核态来完成真正数据读取,而读取又分为两个
阶段,分别为:

  • 等待数据阶段
  • 复制数据阶段

image-20230421154259468

3.阻塞 IO

image-20230421154606192

image-20230421154752364

4.非阻塞 IO

image-20230421154616125

5.多路复用

image-20230421154628662

image-20230421154806267

6.异步 IO

AIO 用来解决数据复制阶段的阻塞问题

  • 同步意味着,在进行读写操作时,线程需要等待结果,还是相当于闲置

  • 异步意味着,在进行读写操作时,线程不必等待结果,而是将来由操作系统来通过回调方式由另外的线程来获

    得结果

异步模型需要底层操作系统(Kernel)提供支持

  • Windows 系统通过 IOCP 实现了真正的异步 IO
  • Linux 系统异步 IO 在 2.6 版本引入,但其底层实现还是用多路复用模拟了异步 IO,性能没有优势

image-20230421155734450

7.零拷贝

1.传统 IO

传统 IO 问题:传统的 lO 将一个文件通过 socket 写出

File f = new File("helloword/data.txt");
RandomAccessFile file = new RandomAccessFile(file, "r");
byte[] buf = new byte[(int)f.Tength()];
file.read(buf);
Socket socket =...;
socket.getOutputStream().write(buf);

内部工作流程是这样的:

image-20230421160027006

  1. java 本身并不具备 IO 读写能力,因此 read 方法调用后,要从 java 程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核缓冲区。这期间用户线程阻塞,操作系统使用 DMA(Direct Memory Access)来实现文件读,期间也不会使用 cpu

  2. 从内核态切换回用户态,将数据从内核缓冲区读入用户缓冲区(即 byte[] buf),这期间 cpu 会参与拷贝,无

    法利用 DMA

  3. 调用 write 方法,这时将数据从用户缓冲区(bytel[] buf)写入 socket 缓冲区,cpu 会参与拷贝

  4. 接下来要向网卡写数据,这项能力 java 又不具备,因此又得从用户态切换至内核态,调用操作系统的写能
    力,使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu

DMA 也可以理解为硬件单元,用来解放 cpu 完成文件 IO

可以看到中间环节较多,java 的 lO 实际不是物理设备级别的读写,而是缓存的复制,底层的真正读写是操作系统
来完成的

  • 用户态与内核态的切换发生了 3 次,这个操作比较重量级
  • 数据拷贝了共 4 次

2.NIO 优化

通过 DirectByteBuff

  • ByteBuffer.allocate(10) HeapByteBuffer
  • ByteBuffer.allocateDirect(10) DirectByteBuffer

image-20230421182614849

大部分步骤与优化前相同,不再赘述。唯有一点:java 可以使用 DirectByteBuffer 将堆外内存映射到 jvm 内存中来
直接访问使用

  • 这块内存不受 jvm 垃圾回收的影响,因此内存地址固定,有助于 IO 读写
  • java 中的 DirectByteBuffer 对象仅维护了此内存的虚引用,内存回收分成两步
    • DirectByteBuffer 对象被垃圾回收,将虚引用加入引用队列
    • 通过专门线程访问引用队列,根据虚引用释放堆外内存
  • 减少了一次数据拷贝,用户态与内核态的切换次数没有减少

3.sendFile 优化

进一步优化(底层采用了 linux 2.1 后提供的 sendFile 方法),java 中对应着两个 channel 调用
transferTo/transferFrom 方法拷贝数据

image-20230421182906793

  1. java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA 将数据读入内核缓冲区,不会使用 cpu
  2. 数据从内核缓冲区传输到 socket 缓冲区,cpu 会参与拷贝
  3. 最后使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu

可以看到

  • 只发生了一次用户态与内核态的切换
  • 数据拷贝了 3 次

4.进一步优化

进一步优化(linux2.4)

image-20230421183206129

  1. java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA 将数据读入内核缓冲区,不

    会使用 cpu

  2. 只会将一些 offset 和 length 信息拷入 socket 缓冲区,几乎无消耗

  3. 使用 DMA 将内核缓冲区的数据写入网卡,不会使用 cpu

整个过程

  • 仅仅只发生了一次用户态与内核态的切换
  • 数据拷贝了 2 次
  • 所谓的【零拷贝】,并不是真正无拷贝,而是在不会拷贝重复数据到 ivm 内存中,

零拷贝的优点有

  • 更少的用户态与内核态的切换
  • 不利用 cpu 计算,减少 cpu 缓存伪共享
  • 零拷贝适合小文件传输> ❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1549794.html

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

相关文章

NIO 应用-网络通信

目录 1. 服务端处理器关注读事件2. 服务端启动类3. 客户端处理器4. 客户端启动类5. 先启动服务端&#xff0c;再启动客户端6. 新建一个服务端处理器&#xff0c;同时关注读事件和写事件7. 启动类8. 先启动服务端&#xff0c;再启动客户端代码下载地址 1. 服务端处理器关注读事件…

人工神经网络--ANN

神经网络是一门重要的机器学习技术。它是目前最为火热的研究方向--深度学习的基础。学习神经网络不仅可以让你掌握一门强大的机器学习方法&#xff0c;同时也可以更好地帮助你理解深度学习技术。 本文以一种简单的&#xff0c;循序的方式讲解神经网络。适合对神经网络了解不多的…

LabVIEW对NI Linux RT应用程序性能进行基准测试

LabVIEW对NI Linux RT应用程序性能进行基准测试 如果应用程序具有苛刻的性能要求&#xff0c;则应为应用程序创建性能基准测试&#xff0c;以确保它满足性能要求。性能要求高度依赖于应用程序&#xff0c;应确定哪些性能指标很重要。下面介绍了典型的实时应用程序性能指标。 如…

NIN网络-Network In Network

图像分类系列&#xff1a; 0、BP前反馈神经网络原理详解 1、Lenet详解 2、Alexnet详解 3、VGG网络结构 4、NIN网络结构 5、Googlenet inception v1 结构详解、inception v2 和 inception v3 参考 https://www.cnblogs.com/makefile/p/nin.html&#xff0c;第一个卷积核是…

机器学习——人工神经网络(NN)

文章链接&#xff1a; 机器学习——卷积神经网络&#xff08;CNN&#xff09; 机器学习——循环神经网络&#xff08;RNN&#xff09; 机器学习——长短期记忆&#xff08;LSTM&#xff09; 机器学习——决策树&#xff08;decision tree&#xff09; 机器学习——随机森林…

NNI (Neural Network Intelligence)简介

是什么 NNI (Neural Network Intelligence) 是一个轻量但强大的自动机器学习&#xff08;AutoML&#xff09;工具包&#xff0c;能帮助用户自动地进行特征工程、神经网络架构搜索、超参调优以及模型压缩。 优势 支持多框架、多训练平台&#xff0c;中文文档&#xff0c;易用…

LabVIEW 设定NI-DAQmx通道的共享变量的轮询速率

LabVIEW 设定NI-DAQmx通道的共享变量的轮询速率 当使用LabVIEW共享变量引擎通过NI-DAQmx通道来发布数据时&#xff0c;希望数据更新速率更快。该如何设定通道对共享变量的轮询速率&#xff1f; 在主机上&#xff0c;有一个叫做Server Polling Rate&#xff08;服务器轮询速率…

神经网络:Network In Network

《Network In Network》论文解读 本文来自于新加坡国立大学&#xff0c;原文地址&#xff1a; https://arxiv.org/abs/1312.4400 概述 本文提出了一种叫Network in Network的网络结构&#xff0c;主要贡献有两个&#xff1a;一是mlpconv网络结构&#xff0c;一是Global averag…

NIO网络通信

阻塞式IO与非阻塞式IO 传统的IO流都是阻塞式的。也就是说&#xff0c;当一个线程调用read() 或write()时&#xff0c;该线程被阻塞&#xff0c;直到有一些数据被读取或写入&#xff0c;该线程在此期间不能执行其他任务。因此&#xff0c;在完成网络通信进行IO操作时&#xff0c…

自动驾驶仿真:Carsim、NI和VTD联合仿真课题二

文章目录 前言一、设备配置1、硬件需求1.1 电脑一&#xff08;Linux系统&#xff09;1.2 电脑二&#xff08;Window系统&#xff09;1.3 NI实时机系统 2、网络配置2.1 IP设置 二、Carsim工程配置1、创建工程1.1 复制NI的模板&#xff0c;创建新工程 2、创建数据库2.1 复制数据库…

【关于NI CAN USB-8473在实际应用中的案例分析】

【NI CAN USB-8473在实际应用中的案例分析】 NI CAN USB-8473是国际上比较先进的一种控制器区域网络&#xff08;Controller Area Network&#xff0c;CAN&#xff09;总线适配器&#xff0c;可实现多个CAN总线与PC的连接。这种适配器的出现&#xff0c;在许多工业控制领域中得…

【python】pyvisa运用——NI-VISA安装和TCP/IP设备配置

文章目录 安装NI-VISA打开NI MAX新建TCPIPpyvisa验证设备连接 安装NI-VISA 安装前关闭杀毒软件&#xff0c;双击NI-VISA runtime17.0后 下载地址&#xff1a;https://download.csdn.net/download/xiakejiqiren/85110993 点击确定 默认安装路径即可&#xff0c;点击unzip&a…

⽹络中的网络(NIN)

《动手学深度学习pytorch》部分学习笔记&#xff0c;仅用作自己复习。 网络中的网络&#xff08;NIN&#xff09; 前几节介绍的LeNet、AlexNet和VGG在设计上的共同之处是&#xff1a;先以由卷积层构成的模块充分抽取空间特征&#xff0c;再以由全连接层构成的模块来输出分类结…

机器学习理论与实战(十二)神经网络

神经网络又火了,因为深度学习火了,所以必须增加个传统神经网络的介绍,尤其是back propagation算法。很简单,俺也就不舞文弄墨的说复杂咯,神经网络模型如(图一)所示: (图一) (图一)中的神经网络模型是由多个感知器(perceptron)分几层组合而成,所谓感知器就是单层的…

打通NI LabVIEW与飞桨工具链,百度携手NI探索工业硬科技新方向

小到一部手机、大到一辆汽车&#xff0c;地上的网络通信、天上的卫星&#xff0c;所有的高科技产品从设计到生产使用的每个环节&#xff0c;都需要进行复杂的测试与验证。因此&#xff0c;自动化测试测量以及对数据的分析能力和效率不仅推动着科技的革新&#xff0c;也具有巨大…

NI9185与NI9234硬件在NI MAX中的设置

NI CDAQ-9185是一款四槽机箱&#xff0c;通过TCP协议&#xff08;网线&#xff09;与电脑相连。NI 9234是声振采集卡&#xff0c;4通道。用来测量来自集成电子压电&#xff08;IEPE&#xff09;和非IEPE传感器信号&#xff0c;例如振动传感器&#xff08;加速度计&#xff09;&…

计算机系统:网络编程

所有的网络应用都是基于相同的基本编程模型&#xff08;客户端-服务器编程模型&#xff09;&#xff0c;有着相似的整体逻辑结构&#xff0c;并且依赖相同的编程接口。 客户端-服务器编程模型 每个网络应用都是基于客户端-服务器模型的。采用这个模型&#xff0c;一个应用是由一…

NoC片上网络

片上网络&#xff1a; 一般采用2D mesh拓扑&#xff0c;其核心为路由算法/流控机制&#xff0c;路由一般使用固定XY或虫孔算法&#xff0c; 目前比较好的NoC开源参考工程为&#xff1a;LisNoC http://www.lisnoc.org/flowcontrol.html ARM官方也有一些NoC应用&#xff1a; …

LabVIEW中MAX在我的网络上不显示“远程系统”选项卡或设备

LabVIEW中MAX在我的网络上不显示“远程系统”选项卡或设备 无法在NI MAX中看到“远程系统”选项卡。 无法在NI MAX的“远程系统”选项卡下看到我的设备。 解决方案 如果设备是实时操作系统&#xff0c;则设备只会显示在NIMeasurement&#xff06;Automation Explorer&#xff0…

网络中的网络:NiN

前面几篇文章介绍的LeNet、AlexNet和VGG在设计上的共同之处是&#xff1a;先以由卷积层构成的模块充分抽取空间特征&#xff0c;再以由全连接层构成的模块来输出分类结果。其中&#xff0c;AlexNet和VGG对LeNet的改进主要在于如 何对这两个模块加宽&#xff08;增加通道数&…