当前位置: 首页 > news >正文

Java NIO channel

channel(通道),byteBuffer(缓冲区),selector(io多路复用),通道FileChannel,SocketChannel的transferTo,transferFrom,MappedByteBuffer实现了零拷贝。
JVM调操作系统方法,read,write,都可以送字节数组。
Java对从操作系统写入和读取的字符数组做了转化为基本数据类型。

channel

表示可以执行IO操作的对象

常用方法:
read(ByteBuffer dst):从通道读取数据到缓冲区。
write(ByteBuffer src):将缓冲区中的数据写入通道。
close():关闭通道。
isOpen():检查通道是否打开。

byteBuffer

缓冲区
ByteBuffer类,
allocate方法,分配堆内内存;
allocateDirect方法,分配堆外内存;
子类MappedByteBuffer,映射虚拟内存;

网络编程

IO多路复用
selector
SelectionKey 通道的状态和兴趣操作(accept,connect,read,write),SelectionKey包含了该通道与Selector` 之间的关联信息
SelectableChannel

在 Java NIO 中,SelectorSelectionKey 是实现非阻塞 I/O 的核心组件,它们共同工作以允许一个或多个线程管理多个通道(如 SocketChannelServerSocketChannel 等)。Selector 用于监控多个 SelectableChannel 对象的 I/O 操作(读、写、连接等),而 SelectionKey 则表示每个注册到 Selector 上的通道的状态和兴趣操作。下面详细解释 SelectionKey 的作用:

1. 注册通道与选择器

当一个 SelectableChannel 被注册到 Selector 上时,会返回一个 SelectionKey 对象。这个 SelectionKey 包含了该通道与 Selector 之间的关联信息。

SelectableChannel channel = ...; // 例如 SocketChannel 或 ServerSocketChannel
Selector selector = Selector.open();
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

2. 兴趣集 (Interest Set)

SelectionKey 的兴趣集指定了我们希望 Selector 监控的 I/O 操作类型。常见的兴趣操作包括:

  • OP_ACCEPT:监听新连接的请求(仅适用于 ServerSocketChannel)。
  • OP_CONNECT:监听连接完成事件(适用于客户端 SocketChannel 的异步连接)。
  • OP_READ:监听可读事件。
  • OP_WRITE:监听可写事件。

你可以通过 interestOps() 方法设置或修改兴趣集:

key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);

3. 就绪集 (Ready Set)

SelectionKey 的就绪集表示当前通道上哪些 I/O 操作已经准备好。每次 Selector 进行选择(调用 select()selectNow())后,它会更新所有注册的 SelectionKey 的就绪集。你可以通过 readyOps() 方法检查哪些操作已经准备好:

int readySet = key.readyOps();
if ((readySet & SelectionKey.OP_READ) == SelectionKey.OP_READ) {// 通道已准备好读取
}
if ((readySet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {// 通道已准备好写入
}

4. 附加对象 (Attached Object)

SelectionKey 允许你将任意对象附加到键上,这通常用于存储与通道相关的上下文信息。你可以使用 attach()attachment() 方法来设置和获取附加对象:

// 设置附加对象
key.attach(someObject);// 获取附加对象
Object attachedObject = key.attachment();

5. 取消注册 (Cancelling the Registration)

如果你不再需要 Selector 监控某个通道,可以调用 cancel() 方法取消注册。这会使得 SelectionKey 在下次 select() 调用时被移除:

key.cancel();

6. 通道和选择器的访问

SelectionKey 提供了方法来访问与其关联的通道和选择器:

  • channel():返回与 SelectionKey 关联的 SelectableChannel
  • selector():返回与 SelectionKey 关联的 Selector
SelectableChannel channel = key.channel();
Selector selector = key.selector();

7. 迭代选择的键

当你调用 Selector.select()selectNow() 后,Selector 会返回一个包含所有就绪键的集合。你可以遍历这些键来处理就绪的 I/O 操作:

Selector selector = Selector.open();
// 注册通道...while (true) {// 等待通道准备就绪selector.select();// 获取所有就绪的 SelectionKeySet<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理接受连接} else if (key.isReadable()) {// 处理读取数据} else if (key.isWritable()) {// 处理写入数据}// 移除已处理的键iterator.remove();}
}

总结

SelectionKey 在 Java NIO 中扮演着至关重要的角色,它不仅记录了通道的兴趣操作和就绪状态,还提供了附加对象的功能,便于开发者在处理 I/O 事件时携带额外的上下文信息。通过 SelectionKey,你可以高效地管理和响应多个通道的 I/O 操作,从而实现高性能的网络应用程序。

Selector

Selector 是 Java NIO(New Input/Output)库中的一个重要组件,它允许单个线程管理多个 SelectableChannel(如 SocketChannelServerSocketChannel 等)。通过 Selector,你可以监控多个通道的 I/O 事件(如连接、读取、写入等),并在这些事件发生时得到通知。这使得编写高效的、非阻塞的网络应用程序成为可能,特别是在需要处理大量并发连接的情况下。

1. 创建 Selector

要使用 Selector,首先需要创建一个实例。可以通过调用 Selector.open() 方法来创建一个新的 Selector

Selector selector = Selector.open();

2. 注册通道

要让 Selector 监控某个 SelectableChannel,你需要将该通道注册到 Selector 上,并指定你感兴趣的 I/O 操作(称为“兴趣集”)。每个注册的通道会返回一个 SelectionKey,这个键包含了通道的状态和相关信息。

// 创建一个 ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); // 设置为非阻塞模式// 绑定端口
serverChannel.bind(new InetSocketAddress(8080));// 将通道注册到 Selector 上,并指定兴趣操作为 OP_ACCEPT
SelectionKey key = serverChannel.register(selector, SelectionKey.OP_ACCEPT);

3. 选择就绪的通道

Selector 提供了几个方法来选择已经准备好进行 I/O 操作的通道:

  • select():阻塞当前线程,直到至少有一个通道准备好了所注册的操作。
  • select(long timeout):阻塞当前线程,最多等待指定的时间(以毫秒为单位),如果在超时时间内没有任何通道准备好,则返回。
  • selectNow():立即返回,不阻塞,检查是否有任何通道准备好。

每次调用这些方法后,Selector 会更新其内部的状态,并返回准备好的通道数量。

int readyChannels = selector.select(); // 阻塞直到有通道准备好
// 或者
int readyChannels = selector.select(1000); // 最多等待1秒
// 或者
int readyChannels = selector.selectNow(); // 立即返回

4. 获取并处理就绪的键

SelectorselectedKeys() 方法返回一个包含所有已准备好通道的 SelectionKey 集合。你可以遍历这个集合,检查每个 SelectionKey 的就绪状态,并根据需要处理相应的 I/O 操作。

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理新的连接请求ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {// 处理读取数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();} else {// 处理接收到的数据buffer.flip();while (buffer.hasRemaining()) {System.out.print((char) buffer.get());}buffer.clear();}} else if (key.isWritable()) {// 处理写入数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.wrap("Hello, Client!".getBytes());clientChannel.write(buffer);}// 移除已处理的键iterator.remove();
}

5. 关闭 Selector

当你不再需要 Selector 时,应该调用 close() 方法来释放相关的资源。这将关闭 Selector 并取消所有注册的通道。

selector.close();

6. 唤醒选择器

如果你在一个线程中调用了 select() 方法并且它正在阻塞,你可以通过调用 wakeup() 方法来唤醒它。这将使 select() 方法立即返回,即使没有通道准备好。

selector.wakeup();

7. 示例代码

以下是一个完整的示例,展示了如何使用 Selector 来处理多个客户端连接:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;public class NIOServer {public static void main(String[] args) throws IOException {// 创建 SelectorSelector selector = Selector.open();// 创建 ServerSocketChannel 并绑定端口ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.configureBlocking(false);serverChannel.bind(new InetSocketAddress(8080));// 注册 ServerSocketChannel 到 Selector,监听连接请求serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server started on port 8080");while (true) {// 等待通道准备就绪selector.select();// 获取所有已准备好通道的 SelectionKeySet<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectedKeys.iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {// 处理新的连接请求ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel clientChannel = server.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("New client connected: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 处理读取数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();System.out.println("Client disconnected: " + clientChannel.getRemoteAddress());} else {// 处理接收到的数据buffer.flip();while (buffer.hasRemaining()) {System.out.print((char) buffer.get());}buffer.clear();}}// 移除已处理的键iterator.remove();}}}
}

总结

Selector 是 Java NIO 中实现高效、非阻塞 I/O 的关键组件。它允许一个线程同时管理多个通道,并在这些通道上发生 I/O 事件时得到通知。通过结合 SelectionKeyByteBuffer,你可以轻松地构建高性能的网络服务器和客户端应用程序。

Java nio的channel相关的api

Java NIO(New Input/Output)库提供了多种 Channel 类,用于非阻塞 I/O 操作和更高效的文件、网络通信。与传统的 I/O 流不同,Channel 是双向的,既可以读取数据也可以写入数据,并且支持直接缓冲区(DirectByteBuffer)以提高性能。以下是 Java NIO 中常见的 Channel 类及其相关 API 的详细介绍:

1. FileChannel

FileChannel 用于对文件进行读写操作。它支持非阻塞模式,并且可以通过 transferTo()transferFrom() 方法实现零拷贝。

  • 创建 FileChannel

    • 通过 RandomAccessFileFileInputStreamFileOutputStream 获取 FileChannel
    RandomAccessFile file = new RandomAccessFile("file.txt", "rw");
    FileChannel channel = file.getChannel();
    
  • 读取和写入

    • 使用 read()write() 方法将数据从 FileChannel 读取到 ByteBuffer 或从 ByteBuffer 写入 FileChannel
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int bytesRead = channel.read(buffer); // 读取数据到缓冲区
    buffer.flip(); // 切换为读模式
    channel.write(buffer); // 将缓冲区中的数据写入通道
    
  • 零拷贝

    • transferTo():将文件内容直接传输到另一个 WritableByteChannel,如 SocketChannel
    • transferFrom():从 ReadableByteChannel 将数据传输到 FileChannel
    long position = 0;
    long count = channel.size();
    channel.transferTo(position, count, socketChannel); // 文件到套接字
    
  • 映射文件

    • map():将文件的一部分或全部映射到内存中,返回一个 MappedByteBuffer
    MappedByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);
    

transferTo():
将文件内容从 FileChannel 直接传输到另一个 WritableByteChannel(如 SocketChannel),而不需要将数据加载到应用程序的内存中。
语法:long transferTo(long position, long count, WritableByteChannel target)
适用于文件到网络套接字的高效传输。
transferFrom():
从 ReadableByteChannel(如 SocketChannel)将数据传输到 FileChannel,同样不需要将数据完全加载到应用程序的内存中。
语法:long transferFrom(ReadableByteChannel src, long position, long count)
适用于从网络套接字接收数据并写入文件。

2. SocketChannel

SocketChannel 用于 TCP 网络通信,支持非阻塞模式和异步连接。

  • 创建 SocketChannel

    • 通过 SocketChannel.open() 创建一个新的 SocketChannel
    SocketChannel socketChannel = SocketChannel.open();
    
  • 连接服务器

    • 使用 connect() 方法连接到远程服务器。
    InetSocketAddress address = new InetSocketAddress("localhost", 8080);
    socketChannel.connect(address);
    
  • 非阻塞模式

    • 调用 configureBlocking(false) 设置为非阻塞模式。
    socketChannel.configureBlocking(false);
    
  • 读取和写入

    • 使用 read()write() 方法与远程服务器进行数据交换。
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int bytesRead = socketChannel.read(buffer); // 读取数据
    buffer.flip(); // 切换为读模式
    socketChannel.write(buffer); // 发送数据
    
  • 关闭连接

    • 调用 close() 方法关闭 SocketChannel
    socketChannel.close();
    

3. ServerSocketChannel

ServerSocketChannel 用于监听传入的 TCP 连接请求,通常与 Selector 一起使用来处理多个客户端连接。

  • 创建 ServerSocketChannel

    • 通过 ServerSocketChannel.open() 创建一个新的 ServerSocketChannel
    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    
  • 绑定端口

    • 使用 bind() 方法绑定到指定的本地地址和端口。
    InetSocketAddress address = new InetSocketAddress(8080);
    serverChannel.bind(address);
    
  • 接受连接

    • 使用 accept() 方法接受新的客户端连接,返回一个新的 SocketChannel
    SocketChannel clientChannel = serverChannel.accept();
    
  • 非阻塞模式

    • 调用 configureBlocking(false) 设置为非阻塞模式。
    serverChannel.configureBlocking(false);
    
  • 注册到 Selector

    • ServerSocketChannel 注册到 Selector 上,监听 OP_ACCEPT 事件。
    SelectionKey key = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    

4. DatagramChannel

DatagramChannel 用于 UDP 网络通信,支持非阻塞模式和异步发送/接收数据包。

  • 创建 DatagramChannel

    • 通过 DatagramChannel.open() 创建一个新的 DatagramChannel
    DatagramChannel datagramChannel = DatagramChannel.open();
    
  • 绑定端口

    • 使用 bind() 方法绑定到指定的本地地址和端口。
    InetSocketAddress address = new InetSocketAddress(8080);
    datagramChannel.bind(address);
    
  • 非阻塞模式

    • 调用 configureBlocking(false) 设置为非阻塞模式。
    datagramChannel.configureBlocking(false);
    
  • 发送和接收数据

    • 使用 send()receive() 方法发送和接收数据包。
    ByteBuffer buffer = ByteBuffer.wrap("Hello, Client!".getBytes());
    InetSocketAddress remoteAddress = new InetSocketAddress("localhost", 8080);
    datagramChannel.send(buffer, remoteAddress); // 发送数据buffer.clear();
    InetSocketAddress senderAddress = (InetSocketAddress) datagramChannel.receive(buffer); // 接收数据
    
  • 注册到 Selector

    • DatagramChannel 注册到 Selector 上,监听 OP_READOP_WRITE 事件。
    SelectionKey key = datagramChannel.register(selector, SelectionKey.OP_READ);
    

5. AsynchronousFileChannel

AsynchronousFileChannel 提供了异步文件 I/O 的功能,可以在不阻塞主线程的情况下执行文件读写操作。

  • 创建 AsynchronousFileChannel

    • 通过 AsynchronousFileChannel.open() 创建一个新的 AsynchronousFileChannel
    Path path = Paths.get("file.txt");
    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
    
  • 异步读取和写入

    • 使用 read()write() 方法异步读取和写入文件内容,返回一个 Future 对象。
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    Future<Integer> readResult = fileChannel.read(buffer, 0); // 异步读取
    Future<Integer> writeResult = fileChannel.write(buffer, 0); // 异步写入
    
  • 完成处理器

    • 可以使用 CompletionHandler 来处理异步操作的结果。
    fileChannel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("Read " + result + " bytes");}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.err.println("Read failed: " + exc.getMessage());}
    });
    

6. Pipe

Pipe 是一种特殊的通道,用于在同一个 JVM 内的不同线程之间传递数据。它由两个部分组成:SinkChannel(写入端)和 SourceChannel(读取端)。

  • 创建 Pipe

    • 通过 Pipe.open() 创建一个新的 Pipe
    Pipe pipe = Pipe.open();
    
  • 获取 SinkChannel 和 SourceChannel

    • 使用 sink()source() 方法分别获取写入端和读取端。
    Pipe.SinkChannel sinkChannel = pipe.sink();
    Pipe.SourceChannel sourceChannel = pipe.source();
    
  • 读取和写入

    • 使用 write()read() 方法在 SinkChannelSourceChannel 之间传递数据。
    ByteBuffer buffer = ByteBuffer.wrap("Hello, Pipe!".getBytes());
    sinkChannel.write(buffer); // 写入数据
    buffer.clear();
    sourceChannel.read(buffer); // 读取数据
    

总结

Java NIO 提供了丰富的 Channel 类,涵盖了文件、TCP、UDP 以及异步 I/O 操作。这些类不仅支持非阻塞模式,还提供了高效的零拷贝和异步操作机制,使得开发者可以构建高性能的网络应用程序和文件处理系统。选择合适的 Channel 类取决于具体的应用场景和需求,例如是否需要处理大量并发连接、是否需要高效的数据传输等。

内存映射文件(MappedByteBuffer)在哪里分配缓冲区?和操作系统的page cache 的交互过程是啥?

内存映射文件(MappedByteBuffer)的缓冲区并不是像普通 ByteBuffer 那样直接在 JVM 的堆空间中分配,而是通过操作系统的虚拟内存管理机制来实现的。具体来说,MappedByteBuffer 的缓冲区是通过操作系统提供的内存映射功能分配的,它实际上映射到了操作系统的 页缓存(Page Cache) 中。下面我们将详细解释 MappedByteBuffer 的分配机制以及它与操作系统页缓存的交互过程。

1. MappedByteBuffer 的缓冲区分配

当调用 FileChannel.map() 方法时,JVM 会请求操作系统将文件的某个区域映射到进程的虚拟地址空间中。这个映射过程由操作系统的内存管理单元(MMU)处理,具体步骤如下:

  • 虚拟地址映射:操作系统会在进程的虚拟地址空间中为文件的指定区域分配一段虚拟地址。这段虚拟地址并不立即对应物理内存中的实际页面,而是一个虚拟映射。

  • 按需加载:只有当程序访问这段虚拟地址中的数据时,操作系统才会根据需要将对应的文件页面从磁盘加载到物理内存中。这个过程称为 缺页中断(Page Fault)。操作系统会检查页表,找到对应的磁盘位置,然后将数据加载到物理内存中,并更新页表以记录新的映射关系。

  • 共享页缓存:加载到物理内存中的页面会被存储在操作系统的 页缓存(Page Cache) 中。页缓存是操作系统用于缓存文件数据的内存区域,它可以加速对文件的读写操作。多个进程可以通过页缓存共享同一份文件数据,从而提高效率。

因此,MappedByteBuffer 的缓冲区并不是直接在 JVM 的堆空间中分配的,而是通过操作系统的虚拟内存管理机制映射到物理内存中的页缓存。这意味着 MappedByteBuffer 的内容实际上是存储在操作系统管理的物理内存中,而不是 JVM 的堆中。

2. MappedByteBuffer 与操作系统页缓存的交互过程

MappedByteBuffer 与操作系统页缓存之间的交互主要体现在以下几个方面:

2.1 读取操作
  • 按需加载:当你通过 MappedByteBuffer 读取文件内容时,JVM 会通过虚拟地址访问数据。如果该虚拟地址对应的页面尚未加载到物理内存中,操作系统会触发 缺页中断,并将所需的页面从磁盘加载到页缓存中。

  • 缓存命中:如果该页面已经在页缓存中(即之前已经被加载过),操作系统可以直接从页缓存中返回数据,而不需要再次从磁盘读取。这大大提高了读取速度,因为访问物理内存的速度远快于访问磁盘。

  • 透明性:对于应用程序来说,读取 MappedByteBuffer 的过程是透明的,你只需要像操作普通内存一样读取数据,而不需要显式地进行 I/O 操作。操作系统会自动处理页面的加载和缓存。

2.2 写入操作
  • 写时复制(Copy-On-Write):如果你使用 READ_WRITEPRIVATE 模式对 MappedByteBuffer 进行写操作,操作系统会根据模式的不同处理写入行为:

    • READ_WRITE 模式:写入的数据会直接反映到文件中。操作系统会将修改后的页面标记为脏页(Dirty Page),并在适当的时候将这些页面同步回磁盘。你可以通过调用 force() 方法强制将修改同步到磁盘。
    • PRIVATE 模式:写入的数据不会影响原始文件。操作系统会使用 写时复制 机制,在你第一次写入某个页面时,操作系统会创建该页面的副本,并将修改应用到副本上,而原始文件保持不变。
  • 脏页管理:当 MappedByteBuffer 中的数据被修改后,操作系统会将这些页面标记为脏页。脏页会保留在页缓存中,直到操作系统决定将其写回到磁盘。通常,操作系统会在系统空闲时或内存压力较大时将脏页同步回磁盘。

  • 同步到磁盘:你可以通过调用 MappedByteBuffer.force() 方法显式地将脏页同步到磁盘。这确保了文件的修改被持久化,避免在系统崩溃时丢失数据。

2.3 换出和换入
  • 换出(Swap Out):当物理内存不足时,操作系统可能会将不常用的页面换出到磁盘上的交换文件(Swap File)。这包括 MappedByteBuffer 映射的页面。换出的过程是透明的,应用程序不会感知到这一变化。当程序再次访问这些页面时,操作系统会重新将它们从交换文件加载回物理内存。

  • 换入(Swap In):当程序访问一个已经被换出的页面时,操作系统会触发 缺页中断,并将该页面从交换文件加载回物理内存。这个过程与从磁盘加载文件页面类似,但性能稍差,因为交换文件通常是位于较慢的存储设备上。

2.4 内存回收
  • 自动回收:当 MappedByteBuffer 对应的 FileChannel 被关闭时,操作系统会自动回收该映射的虚拟地址空间。然而,操作系统并不会立即释放物理内存中的页面,除非这些页面不再被任何进程使用。这意味着即使 MappedByteBuffer 已经被垃圾回收,操作系统仍然可能保留这些页面在页缓存中,以备后续使用。

  • 手动清理:在某些情况下,你可能希望显式地清理 MappedByteBuffer 映射的内存。Java 8 及之后版本提供了一个 sun.misc.Cleaner 类,可以通过反射调用其 clean() 方法来强制清理 MappedByteBuffer。然而,这种方法是非标准的,且不推荐在生产环境中使用。

3. MappedByteBuffer 与 JVM 堆空间的关系

MappedByteBuffer 的缓冲区并不在 JVM 的堆空间中分配,因此它的大小不受 JVM 堆大小的限制。相反,MappedByteBuffer 的大小取决于操作系统的虚拟内存管理和可用的物理内存。这意味着你可以映射非常大的文件,而不会导致 JVM 堆溢出(OutOfMemoryError)。

然而,虽然 MappedByteBuffer 不占用 JVM 堆空间,但它仍然会占用操作系统的虚拟地址空间。在 32 位系统上,虚拟地址空间是有限的(通常为 4GB),因此映射过大的文件可能会导致虚拟地址空间耗尽。在 64 位系统上,虚拟地址空间非常大,这个问题较少发生。

4. 优点与注意事项

优点:
  • 高性能:通过利用操作系统的页缓存和按需加载机制,MappedByteBuffer 可以显著提高文件 I/O 的性能,尤其是在处理大文件时。
  • 简化编程模型:你可以像操作普通内存一样访问文件内容,代码更加简洁。
  • 异步加载:操作系统会在需要时自动将文件的数据从磁盘加载到内存,而不是一次性加载整个文件,节省了内存并提高了响应速度。
  • 支持随机访问:你可以直接访问文件的任意位置,而不需要从头开始读取。
注意事项:
  • 内存使用:虽然 MappedByteBuffer 不占用 JVM 堆空间,但它会占用操作系统的虚拟地址空间。在 32 位系统上,虚拟地址空间是有限的,因此需要注意不要映射过大的文件。
  • 持久性问题:在某些情况下,操作系统可能不会立即将修改后的数据写回到磁盘,这可能导致数据丢失。你可以使用 force() 方法强制将修改同步到磁盘。
  • 安全性问题:由于 MappedByteBuffer 允许直接访问文件内容,因此在多线程环境中使用时需要注意线程安全问题,避免多个线程同时修改同一部分数据。
  • 内存回收MappedByteBuffer 对应的内存映射区域不会自动释放,即使 MappedByteBuffer 对象被垃圾回收。为了确保资源正确释放,建议在不再需要时显式地关闭相关的 FileChannel

5. 总结

MappedByteBuffer 是 Java NIO 中一个强大的工具,它通过内存映射文件技术提供了高效的文件 I/O 操作。MappedByteBuffer 的缓冲区并不是直接在 JVM 的堆空间中分配的,而是通过操作系统的虚拟内存管理机制映射到物理内存中的页缓存。操作系统负责管理页面的加载、换入换出和脏页同步,确保文件数据的高效访问和持久化。理解 MappedByteBuffer 与操作系统页缓存的交互过程,可以帮助开发者更好地优化文件 I/O 性能,并避免潜在的问题。

linux mmap()+write()` 的组合是一种实现零拷贝(或称为“半零拷贝”)的方法

mmap() + write() 的组合是一种实现零拷贝(或称为“半零拷贝”)的方法,它通过将文件内容映射到进程的地址空间,然后使用 write() 系统调用来发送这些数据。这种方式减少了从内核空间到用户空间的数据复制次数,但仍然需要一次从用户空间到套接字缓冲区的复制。下面是这个过程的一个简化图示:

+---------------------+         +---------------------+
|                     |         |                     |
|   用户空间 (User)   |         |  内核空间 (Kernel)  |
|                     |         |                     |
|  +--------------+   |         |  +--------------+   |
|  | mmap() 映射  |   |         |  | 文件缓存     |   |
|  | 区域         |<--+---------+->| (Page Cache) |   |
|  +--------------+   |         |  +--------------+   |
|                     |         |                     |
|  +--------------+   |         |  +--------------+   |
|  | write()      |   |         |  | 套接字缓冲区 |   |
|  | 发送数据     |---+-------->+->| (Socket Buf) |   |
|  +--------------+   |         |  +--------------+   |
|                     |         |                     |
+---------------------+         +---------------------+

具体步骤说明

  1. 文件映射 (mmap()):

    • 应用程序调用 mmap() 系统调用,将文件的一部分或全部映射到进程的虚拟地址空间。
    • 这个操作不会立即将文件内容加载到内存中,而是创建了一个虚拟内存区域,当应用程序访问这个区域时,操作系统会按需将文件内容加载到物理内存(页缓存)中。
  2. 数据传输 (write()):

    • 应用程序可以直接访问 mmap() 创建的内存区域,读取文件内容。
    • 然后,应用程序调用 write() 系统调用,将这些数据写入一个套接字或其他文件描述符。
    • 在这一步,数据会被从用户空间复制到内核空间的套接字缓冲区中,准备发送给网络接口卡(NIC)或其他目的地。

图解中的箭头表示

  • 虚线箭头 表示 mmap() 操作创建了用户空间和内核空间之间的映射关系,但并不立即发生数据复制。
  • 实线箭头 表示实际的数据复制操作,即当 write() 被调用时,数据从用户空间被复制到内核空间的套接字缓冲区。

注意事项

  • mmap()write() 组合虽然减少了部分数据复制,但并不是完全的零拷贝,因为 write() 仍然需要将数据从用户空间复制到内核空间。
  • 对于大文件或大量数据的传输,这种方法可以显著减少CPU的负担和上下文切换,提高性能。
  • 使用 mmap() 时需要注意文件大小和系统资源的限制,以及正确处理内存映射区域的同步问题。

如果你想要实现真正的零拷贝,可以考虑使用 sendfile() 或者 splice() 等更先进的系统调用,它们可以在某些情况下避免用户空间与内核空间之间的数据复制。

相关文章:

Java NIO channel

channel(通道)&#xff0c;byteBuffer(缓冲区)&#xff0c;selector&#xff08;io多路复用&#xff09;&#xff0c;通道FileChannel,SocketChannel的transferTo,transferFrom,MappedByteBuffer实现了零拷贝。 JVM调操作系统方法&#xff0c;read,write&#xff0c;都可以送字…...

B3631 单向链表-模拟链表

来源 &#xff1a;题目链接-洛谷 B3631 单向链表 单向链表 题目描述 实现一个数据结构&#xff0c;维护一张表&#xff08;最初只有一个元素 1 1 1&#xff09;。需要支持下面的操作&#xff0c;其中 x x x 和 y y y 都是 1 1 1 到 1 0 6 10^6 106 范围内的正整数&…...

【C++】格式化输出详解:掌握 cout 的进阶用法

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;格式化输出的理论概述&#x1f4af;控制输出宽度和填充字符setw 操作符setfill 操作符 &#x1f4af;控制浮点数的显示格式fixed 与 scientificsetprecision &#x1f4af;…...

【NoSQL数据库】Hbase基本操作——数据库表的增删改查

目录 一、Hbase原理 二、HBase数据库操作 三、遇到的问题和解决方法 一、Hbase原理 HBase的数据模型&#xff1a; 行键 时间戳 列族&#xff1a;contents 列族&#xff1a;anchor 列族&#xff1a;mime “com.cnn.www” T9 Achor:cnnsi.com”CNN” T8 Achor:…...

同步fifo

同步FIFO FIFO即是一种先进先出的数据缓存器。同步FIFO指的是数据的写入和读出的时钟是同一个时钟。异步 FIFO 有两个时钟信号&#xff0c;读和写逻辑用的各自的读写时钟。 FIFO没有外部读写地址线&#xff0c;使用起来简单。但是缺点就是只能先入先出&#xff0c;数据地址由…...

肌肉骨骼肿瘤治疗市场:潜力无限,未来可期

肌肉骨骼肿瘤治疗作为现代医学的重要分支&#xff0c;专注于应对骨骼和肌肉系统中的良性和恶性肿瘤。随着全球人口老龄化和生活方式的改变&#xff0c;肌肉骨骼疾病日益成为公共卫生的重要问题。与此同时&#xff0c;医疗技术的进步和患者对高质量医疗服务的需求不断推动该市场…...

高考倒计时:用倒计时软件 为梦想加油 可用于教室黑板或者电脑上

高考&#xff0c;这个被无数学子视为人生重要转折点的考试&#xff0c;即将来临。每一年的六月&#xff0c;都充满了紧张与期待。如何在这场人生的战役中取得胜利&#xff1f;除了日常的勤奋学习&#xff0c;科学的复习计划和心态调整外&#xff0c;一款好用的倒计时软件&#…...

人工智能学习用的电脑安装cuda、torch、conda等软件,版本的选择以及多版本切换

接触人工智能的学习三个月了&#xff0c;每天与各种安装包作斗争&#xff0c;缺少依赖包、版本高了、版本低了、不兼容了、系统做一半从头再来了。。。这些都是常态。三个月把单位几台电脑折腾了不下几十次安装&#xff0c;是时候总结一下踩过的坑和积累的经验了。 以一个典型的…...

BERT模型的输出格式探究以及提取出BERT 模型的CLS表示,last_hidden_state[:, 0, :]用于提取每个句子的CLS向量表示

说在前面 最近使用自己的数据集对bert-base-uncased进行了二次预训练&#xff0c;只使用了MLM任务&#xff0c;发现在加载训练好的模型进行输出CLS表示用于下游任务时&#xff0c;同一个句子的输出CLS表示都不一样&#xff0c;并且控制台输出以下警告信息。说是没有这些权重。…...

InfluxDB 集成 Grafana

将InfluxDB集成到Grafana进行详细配置通常包括以下几个步骤&#xff1a;安装与配置InfluxDB、安装与配置Grafana、在Grafana中添加InfluxDB数据源以及创建和配置仪表板。以下是一个详细的配置指南&#xff1a; 一、安装与配置InfluxDB 下载与安装&#xff1a; 从InfluxDB的官…...

Vue跨标签通讯(本地存储)(踩坑)

我司有一个需求【用户指引】 需求是根标签有一个用户指引总开关&#xff0c;可以控制页面所有的用户指引是否在页面进入后初始是否默认打开&#xff0c;但是有些页面会新开标签这就设计到跨标签通讯了 我采取的方案是本地存储 重点:首先本地存储在页面是同源(即域名协议端口三…...

掌握创意之钥:全面解析HTML5 Canvas

在数字时代&#xff0c;表达创意的方式多种多样&#xff0c;而 HTML5 中的 <canvas> 元素无疑为网页开发者提供了一个强大的工具箱。无论你是想要创建动态图表、互动游戏还是复杂的可视化应用&#xff0c;掌握 Canvas 的基本用法都是迈向成功的关键一步。本文将带你一步步…...

mac port 安装redis 并设置为系统服务 自定义配置方法

mac系统中&#xff0c;port 包管理工具比brew的速度快N倍&#xff0c;今天就给大家分享一下在macos系统中如何使用 port安装 redis数据库并配置为服务自动启动和自定义redis.conf配置的方法。 1. 安装redis sudo port install redis 2. 启动redis服务 sudo port load redis …...

Agent AI: Surveying the Horizons of Multimodal Interaction---摘要、引言、代理 AI 集成

题目 智能体AI:多模态交互视野的考察 论文地址&#xff1a;https://arxiv.org/abs/2401.03568 图1&#xff1a;可以在不同领域和应用程序中感知和行动的Agent AI系统概述。Agent AI是正在成为通用人工智能&#xff08;AGI&#xff09;的一个有前途的途径。Agent AI培训已经证…...

二百七十八、ClickHouse——将本月第一天所在的那一周视为第一周,无论它是从周几开始的,查询某个日期是本月第几周

一、目的 ClickHouse指标表中有个字段week_of_month&#xff0c;含义是这条数据属于本月第几周。 而且将本月第一天所在的那一周视为第一周&#xff0c;无论它是从周几开始的。比如2024-12-01是周日&#xff0c;即12月第一周。而2024-12-02是周一&#xff0c;即12月第二周 二…...

Unity 相机旋转及角度限制

前言 由于欧拉角具有直观的可读性&#xff0c;做相机旋转时选择修改eulerAngles 来实现旋转&#xff0c;但实际效果与预期稍有不同&#xff0c;这是因为欧拉角受到万向锁&#xff08;Gimbal Lock&#xff09;的影响&#xff0c;在赋值时需要对输入的角度进行调整。 if (value…...

基于CentOS系统利用Kamailio搭建企业级SIP服务器

一、Kamailio简介 Kamailio是一款开源的SIP服务器&#xff0c;具有高性能、可扩展、模块化等特点。它广泛应用于VoIP、即时通讯、视频会议等领域。Kamailio支持多种操作系统&#xff0c;如Linux、FreeBSD等&#xff0c;可以与其他开源项目&#xff08;如 Asterisk、FreeSWITCH…...

部署项目报错

vue2项目部署后 Error: Cannot find module /views/*** 1.起因 登录页、首页等静态页面可以正常进入&#xff0c;后端访问也正常&#xff0c;可以获取到验证码。 但是登录之后会发现首页空白或者进入不到首页 F12查看有报错信息&#xff1a;Error: Cannot find module ‘/v…...

【AIGC】大模型面试高频考点-位置编码篇

【AIGC】大模型面试高频考点-位置编码篇 &#xff08;一&#xff09;手撕 绝对位置编码 算法&#xff08;二&#xff09;手撕 可学习位置编码 算法&#xff08;三&#xff09;手撕 相对位置编码 算法&#xff08;四&#xff09;手撕 Rope 算法&#xff08;旋转位置编码&#xf…...

钓鱼攻击详解:鱼叉攻击与水坑攻击

钓鱼攻击详解&#xff1a;鱼叉攻击与水坑攻击 在现代网络安全领域中&#xff0c;钓鱼攻击&#xff08;Phishing&#xff09;是一种最常见且有效的攻击手段。它通过欺骗用户&#xff0c;引导其泄露敏感信息或执行恶意操作&#xff0c;从而为攻击者打开大门。本文将深入介绍两种…...

如何在自动化安全测试中,实现多工具集成与数据融合,以提高对Spring Boot应用程序安全漏洞的检测效率与准确性?

为了在自动化安全测试中实现多工具集成与数据融合&#xff0c;以提高对Spring Boot应用程序安全漏洞的检测效率与准确性&#xff0c;可以采取以下策略和方法&#xff1a; 文章目录 1. 工具选择与集成2. 数据标准化与聚合3. 数据分析与融合4. 持续改进5. 实施示例 1. 工具选择与…...

框架篇面试

一、Spring框架中的单例bean的安全性 Spring框架中有一个Scope注解&#xff0c;默认的值就是singleton&#xff0c;单例的&#xff1b;因为一般在spring的bean中注入的都是无状态的对象&#xff0c;所以没有线程安全问题。但是如果在bean中定义了可修改的成员变量&#xff0c;…...

STM32滴答定时器SysTick理解+时基设置(4.1)

文章目录 1. 什么是滴答定时器&#xff1f;2. SysTick定时器初始化2.1 systick定时器时钟源&#xff1f;2.2 定时器四个寄存器 3 函数设置3.1SysTick_Config&#xff08;uint32_t ticks&#xff09;函数3.2初始化函数 4. 延时函数实现4.1 ms延时思路及实现4.2 us延时 1. 什么是…...

数字化时代下的企业合规管理:全球化背景下的挑战与机遇

在全球化浪潮的推动下&#xff0c;企业合规管理已成为企业发展中不可或缺的一部分。随着各国法规日益严格&#xff0c;以及数字化技术的飞速发展&#xff0c;企业在扩展业务的同时&#xff0c;也面临着越来越多的合规挑战。有效的合规管理不仅有助于提高企业的管理水平和运营效…...

读《Effective Java》笔记 - 条目17

条目17&#xff1a;使可变性最小化 为什么要使可变性最小化&#xff1f; 不可变对象天然是线程安全的&#xff0c;可以在多个线程之间安全共享。而可变对象需要添加额外的同步机制保证线程安全。不可变对象一旦创建就不会改变&#xff0c;便于追踪和理解代码。而可变对象的状态…...

对比json数据是否变化

在 JavaScript 中&#xff0c;你可以使用多种方法来对比两个 JSON 数据是否发生变化。以下是几种常见的方式&#xff1a; 1. 使用 JSON.stringify 最简单的方法是将两个 JSON 对象序列化为字符串&#xff0c;并比较这些字符串。但需要注意的是&#xff0c;这种方法对于对象属…...

云计算实验室建设方案

一、云计算实验室建设方案 云计算实验教学整体解决方案&#xff0c;包括&#xff1a;云计算服务器集群、云计算实训平台、实训课程体系、行业实战课程系统、行业数据等&#xff0c;系统性地解决云计算实训教学的痛点问题。 【硬件系统】云计算实训一体机 云计算实训一体机是唯…...

一、理论基础-PSI

之前参加了隐语第2期&#xff0c;对隐语SecretFlow框架有了大致的了解&#xff0c;这次参加隐语第4期&#xff0c;学习下PSI和PIR。 一、PSI定义 首先介绍PSI的定义&#xff0c;PSI&#xff08;隐私集合求交&#xff0c;Private Set Intersection即PSI)是安全多方计算&#x…...

C++学习0.2: RAII

引用&#xff1a; 【代码质量】RAII在C编程中的必要性_raii 在c中的重要性-CSDN博客 C RAII典型应用之lock_guard和unique_lock模板_raii lock-CSDN博客 前言: 常用的线程间同步/通信&#xff08;IPC&#xff09;方式有锁&#xff08;互斥锁、读写锁、自旋锁&#xff09;、…...

机器学习基础

了解机器学习的基本概念&#xff0c;如监督学习、无监督学习、强化学习、模型评估指标&#xff08;准确率、召回率、F1分数等&#xff09;。 机器学习&#xff08;Machine Learning&#xff0c;ML&#xff09;是人工智能&#xff08;AI&#xff09;的一个分支&#xff0c;它使计…...

传输层TCP_三次握手四次挥手的过程

三次握手四次挥手 三次握手 三次握手...

AI主流的生成式工作流框架

根据搜索结果&#xff0c;以下是一些2024年比较主流的生成式工作流框架&#xff1a; 1. LangChain&#xff1a;LangChain是一个用于构建生成式AI工作流的开发框架&#xff0c;它支持多种语言模型、工具、数据源及其他系统的集成。 2. DSPy&#xff1a;DSPy是一个生成式AI工作…...

【WRF后处理】WRF时区(UTC)需转化为北京时间(CST)!!!

目录 WRF运行时间标准注意事项-本地时区问题 输入数据&#xff1a;ERA5时间标准ERA5数据和WRF模型需要转换为北京时间&#xff01;&#xff01;&#xff01;北京时间&#xff08;CST&#xff09;与协调世界时&#xff08;UTC&#xff09;的关系转换方法 参考 WRF运行时间标准 …...

Qt 2D绘图之五:图形视图框架的结构、坐标系统和框架间的事件处理与传播

参考文章链接: Qt 2D绘图之五:图形视图框架的结构和坐标系统 Qt 2D绘图之六:图形视图框架的事件处理与传播 图形视图框架的结构 在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们。但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动、…...

游戏引擎学习第34天

仓库:https://gitee.com/mrxiao_com/2d_game #这天内容比较多 开场介绍 游戏开发行业的基础是使用C和C编程&#xff0c;这是当今几乎所有游戏的开发标准。市面上广受欢迎的游戏&#xff0c;如《使命召唤》或《侠盗猎车手》&#xff0c;它们的底层代码和引擎几乎无一例外地采…...

深度学习笔记——模型压缩和优化技术(蒸馏、剪枝、量化)

本文详细介绍模型训练完成后的压缩和优化技术&#xff1a;蒸馏、剪枝、量化。 文章目录 1. 知识蒸馏 (Knowledge Distillation)基本概念工作流程关键技术类型应用场景优势与挑战优势挑战 总结 2. 权重剪枝 (Model Pruning)基本原理二分类1. 非结构化剪枝&#xff08;Unstructur…...

[在线实验]-RabbitMQ镜像的下载与部署

镜像下载 docker的rabbitmq镜像资源-CSDN文库 加载镜像 docker load --input rabbitmq.tar 给镜像打标签 这里发现镜像名为none&#xff0c;需要给镜像重命名下 docker tag [镜像id] [新镜像名称]:[新镜像标签] docker tag ebaf409ffbe2 rabbitmq:management 运行镜像…...

Netty 入门应用:结合 Redis 实现服务器通信

在上篇博客中&#xff0c;我们了解了 Netty 的基本概念和架构。本篇文章将带你深入实践&#xff0c;构建一个简单的 Netty 服务端&#xff0c;并结合 Redis 实现一个数据存取的示例。在这个场景中&#xff0c;Redis 作为缓存存储&#xff0c;Netty 作为服务端处理客户端请求。通…...

推荐 编译器c++

网页型 https://www.acgo.cn/playground C 在线工具 | 菜鸟工具 AcWing - 在线题库 ZJYYC在线测评系统 少儿编程竞赛在线学习 登录 - JOYSKID 余博士教编程_酷哥OJ_酷哥爱编程_酷哥创客AI编程 登录 - Luogu Spilopelia 软件型 DEV-c Dev C软件下载...

【新品发布】ESP32-P4开发板 —— 启明智显匠心之作,为物联网及HMI产品注入强劲动力

核心亮点&#xff1a; ESP32-P4开发板&#xff0c;是启明智显精心打造的一款高性能物联网开发板。它专为物联网项目及HMI&#xff08;人机界面&#xff09;产品而设计&#xff0c;旨在为您提供卓越的性能和稳定可靠的运行体验。 强大硬件配置&#xff1a; 双核400MHz RISC-V处…...

MeterSphere 使用脚本处理数据

1、前置/后置脚本 支持BeanShell(JSR223)、python、groovy、JavaScript脚本语言&#xff0c;推荐BeanShell(JSR223)。 在前置脚本中可以直接引用JMeter 预定义对象&#xff0c;例如&#xff1a; -- log&#xff1a;用于在脚本执行过程中打印日志 //打印“Hello World!”到info…...

如何获取谷歌新闻API密钥?

在信息获取和新闻传播领域&#xff0c;快速获取最新的新闻动态至关重要。谷歌新闻API为开发者提供了强大的工具&#xff0c;能够方便地集成全球各类新闻内容。通过使用该API&#xff0c;开发者可以实现对新闻的实时访问和管理&#xff0c;为用户提供丰富的信息服务。本文将指导…...

【全网最新】若依管理系统基于SpringBoot的前后端分离版本开发环境配置

目录 提前准备&#xff1a; 下载源代码 设置依赖 设置后台连接信息 运行后台 运行前端 安装npm依赖 启动前端 登录网页客户端 提前准备&#xff1a; 1、安装mysql 5以上就可以。 2、安装redis. 3、安装npm npm下载地址&#xff1a;https://nodejs.org/dist/v22.12…...

备赛蓝桥杯--算法题目(3)

1. 2的幂 231. 2 的幂 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isPowerOfTwo(int n) {return n>0&&n(n&(-n));} }; 2. 3的幂 326. 3 的幂 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isPowerOfT…...

如何解决 java.nio.charset.CoderMalfunctionError: 编码器故障错误问题?亲测有效的解决方法!

java.nio.charset.CoderMalfunctionError 是一个在 Java 中相对较少遇到的异常&#xff0c;通常与字符编码转换过程中的错误有关。当 Java 程序在进行字符编码转换时&#xff0c;遇到无法处理的字符或编码故障时&#xff0c;就会抛出该异常。 1. 问题描述 java.nio.charset.C…...

电气自动化 基于PLC控制的四路抢答器设计

摘要 本文描述了一款用三菱FX3U-48M可编程控制器设计的四路抢答器的系统构成、设计思路和功能。此抢答系统除了有基本抢答功能之外&#xff0c;还有计时、计算得分、亮灯提提示以及蜂鸣提醒功能。程序中设定答题时间&#xff0c;在主持人未按下开始抢答按钮之前&#xff0c;选…...

GA优化后的RBF神经网络

遗传算法&#xff08;Genetic Algorithm, GA&#xff09;优化后的RBF&#xff08;Radial Basis Function&#xff09;神经网络是一种结合进化算法与神经网络的混合模型&#xff0c;用于改进RBF神经网络的性能。以下是该模型的基本原理和相关公式&#xff1a; clear all close a…...

Scala:正则表达式

object test03 {//正则表达式def main(args: Array[String]): Unit {//定义一个正则表达式//1.[ab]:表示匹配一个字符&#xff0c;或者是a&#xff0c;或者是b//2.[a-z]:表示从a到z的26个字母中的任意一个//3.[A-Z]:表示从A到Z的26个字母中的任意一个//4.[0-9]:表示从0到9的10…...

vulnhub靶场之【hacksudo】1.0.1

前言 靶机&#xff1a;hacksudo 192.168.1.45 攻击&#xff1a;kali 192.168.1.16 都是虚拟机环境&#xff0c;桥接模式 主机发现 使用netdiscover或者arp-scan -l扫描 netdiscover -r 192.168.1.1/24信息收集 使用nmap扫描 因为看到2222是ssh服务&#xff0c;所以又扫…...

第4章:颜色和背景 --[CSS零基础入门]

在 CSS 中&#xff0c;颜色和背景属性是用于美化网页元素的重要工具。你可以通过多种方式定义颜色&#xff0c;并且可以设置元素的背景颜色、图像、渐变等。以下是关于如何在 CSS 中使用颜色和背景的一些关键点和示例。 1.颜色表示法 当然&#xff01;以下是使用不同颜色表示…...