<摘要>
本文对 Unix/Linux 系统中的 writev 系统调用进行了全面深入的解析。内容涵盖了其产生的背景(从传统 write 的局限性到分散/聚集 I/O 概念的引入)、核心概念(如 struct iovec、系统调用流程)。重点剖析了其设计意图:减少系统调用次数以提升性能、简化编程模型、以及原子性操作的优点。通过两个详尽的代码实例(HTTP 响应头和日志记录系统)及其 Makefile、编译运行步骤,生动展示了其应用场景和实现流程。文章结合 TCP/IP 协议栈,深入解析了 writev 与网络交互的内部机制,并通过时序图清晰描绘了数据从用户缓冲区到网络报文的完整旅程。最后,通过对比表格总结了其优缺点,并提供了选型建议,为开发者深入理解和正确使用 writev 提供了系统性的指导。


<解析>

1. 背景与核心概念

1.1 历史背景与演进脉络

在计算技术的早期阶段,输入/输出(I/O)操作是程序性能的主要瓶颈之一。传统的 Unix I/O 模型建立在readwrite这两个基本系统调用之上。它们的功能非常直观:read将数据从文件描述符(如文件、套接字)读入一个连续的内存缓冲区,而write则将一个连续的内存缓冲区中的数据写入文件描述符。

这种“一个系统调用,一个缓冲区”的模型在很长一段时间内都是主流。然而,随着网络应用和高性能服务器的发展,其局限性日益凸显。许多应用场景天然地需要处理非连续的多块数据:

  • 网络协议栈:例如,一个 HTTP 响应可能由协议头(Header)和实体内容(Body)组成,这两部分数据通常存储在不同的内存区域(例如,头是常量字符串,体是动态读取的文件内容或数据库查询结果)。使用传统的write,服务器需要先发送头,再发送体,这至少需要两次系统调用。
  • 数据库系统:一条记录可能由多个字段组成,这些字段分散在不同的数据结构中。在写入日志文件(WAL)或进行网络传输时,需要将这些分散的字段组合起来。
  • 科学计算:大型矩阵或数组可能以非连续块的形式存储。

writev出现之前,开发者主要有两种应对策略:

  1. 多次系统调用(Multiple write calls):对每一块数据分别调用write。这种方法简单,但性能差。系统调用本身具有不可忽视的开销,因为它需要从用户态切换到内核态,处理上下文,然后再切换回来。频繁的切换会消耗大量的 CPU 周期。此外,对于网络套接字,多次小数据的write调用可能会导致著名的“Nagle算法”与“TCP_CORK”选项的交互问题,产生不必要的网络报文延迟。
  2. 内存拷贝(Memory Copy):使用一个大的临时缓冲区,在用户空间使用memcpy将多块数据拼接成一个连续的数据块,然后只调用一次write。这种方法减少了系统调用,但代价是多次内存拷贝。内存拷贝同样需要 CPU 时间,尤其当数据量很大时,这种开销会非常显著,而且还需要管理临时缓冲区的生命周期,增加了程序的复杂性。

为了从根本上解决这个问题,分散/聚集 I/O(Scatter/Gather I/O)的概念被引入操作系统。该技术允许一次系统调用操作多个分散的内存缓冲区。对应的系统调用就是readv(聚集读)和writev(分散写)。

  • readv:从文件描述符读入数据,并分散地存储到多个缓冲区中。
  • writev:从多个缓冲区聚集数据,并一次性写入文件描述符。

writev系统调用首次出现在 BSD 4.2 Unix 中,后来被 POSIX.1 标准采纳,成为如今所有现代 Unix-like 系统(包括 Linux、macOS 和各种BSD)的标准接口。

1.2 核心概念与关键术语
  • 分散/聚集 I/O (Scatter/Gather I/O):一种输入输出模型,允许单个系统调用从多个内存缓冲区读取数据(聚集)或将数据写入多个内存缓冲区(分散)。它是高性能服务器编程的关键技术之一。
  • 系统调用 (System Call):操作系统内核为运行在用户空间的程序提供的接口。是用户程序请求内核执行特权操作(如 I/O)的唯一方式。writev就是一个系统调用。
  • struct iovec:这是 writev 操作的核心数据结构,用于描述一个内存缓冲区。它在头文件 <sys/uio.h> 中定义。
    struct iovec {void   *iov_base;  /* Pointer to the start of the buffer. */size_t  iov_len;   /* Size of the buffer in bytes. */
    };
    
    • iov_base:指向缓冲区起始地址的指针。
    • iov_len:该缓冲区的长度。
  • 文件描述符 (File Descriptor):一个非负整数,用于标识一个打开的文件、套接字、管道或其他 I/O 资源。writev 的第一个参数就是一个文件描述符。
  • 原子性 (Atomicity):这是 writev 一个非常重要的特性。对于普通文件,它意味着此次写操作的数据不会与其他进程的写操作交织在一起。对于管道和套接字(在 FIFO 模式下),它进一步保证了一次 writev 调用所写入的数据将会被一次 read 调用完整读取(只要请求的字节数足够多),不会被拆散。这对于基于消息的协议至关重要。

2. 设计意图与考量

writev的设计并非偶然,其背后蕴含着对性能、编程模型和可靠性的深刻考量。

2.1 核心目标:性能优化

这是设计writev最直接、最主要的目标。它通过两种方式提升性能:

  1. 减少系统调用次数:这是最显著的收益。将 N 次 write 调用合并为 1 次 writev 调用,减少了 N-1 次用户态到内核态的上下文切换开销。在内核处理速度极快而系统调用相对昂贵的场景下(如高性能网络服务器),这种优化效果极其明显。
  2. 减少内存拷贝:避免了用户空间“申请临时缓冲区 -> 多次memcpy -> write -> 释放缓冲区”的繁琐过程。数据直接从其原本的位置被内核读取并发送,节省了 CPU 周期和内存带宽。
2.2 核心目标:简化编程模型

writev 允许程序直接操作分散的数据结构,而无需为了 I/O 操作而去改变它们的内存布局或进行额外的拼接。这使得程序逻辑更清晰,更符合“零拷贝”(Zero-copy)的优化思想。代码不再需要关心如何管理那个临时的、仅用于拼接的缓冲区,减少了出错的可能(如缓冲区溢出)。

2.3 具体考量因素
  1. 原子性保证:如前所述,对于管道和套接字,原子性是一个关键特性。设计者确保writev的行为是原子的,这简化了基于消息的协议实现。接收方可以确信一次read调用获取的数据正好是发送方一次writev调用发送的完整消息单元(在合理缓冲区大小下),而不会出现消息被截断或粘合的情况。
  2. 参数设计writev的接口设计得非常通用。
    ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
    
    • fd:目标文件描述符,兼容所有类型。
    • iov:指向iovec结构数组的指针,可以描述任意数量、任意位置、任意大小的缓冲区。
    • iovcnt:指定数组中元素的数量,操作系统通常会对其上限进行限制(如 Linux 的 IOV_MAX,通常为 1024)。这个参数防止了数组越界,提供了安全性。
      这种设计使其能够适应几乎所有的分散输出场景。
  3. 内核实现效率:内核在处理writev时,需要遍历iov数组,将每个缓冲区地址和长度信息映射到内核空间,然后安排输出顺序。这个开销远小于执行多次完整的write系统调用。对于网络套接字,内核最终通常会将所有分散的数据收集起来,填充到一个或多个 TCP/IP 报文段中再发送出去,这个过程对用户是透明的。
2.4 权衡与局限性
  • 不总是最佳选择:如果数据本身已经是连续的,那么直接使用write显然更简单、更直接。使用writev来处理单块数据反而增加了不必要的复杂性(需要构建iovec数组)。
  • 平台依赖性:虽然writev是 POSIX 标准,但其性能表现和某些具体限制(如IOV_MAX的具体值)可能因操作系统实现而异。
  • 调试复杂性:由于数据来源是分散的,在调试 I/O 问题时,定位是哪个缓冲区出的问题可能会比处理单个缓冲区稍显复杂。

3. 实例与应用场景

下面通过两个经典的现实案例来展示writev的应用。

3.1 实例一:HTTP 服务器发送响应

这是writev最经典的应用场景。一个 HTTP 响应通常由状态行、多个头部字段、一个空行和响应体组成。这些部分通常来源于不同的地方。

应用场景:一个简单的 HTTP/1.1 服务器需要向客户端发送一个成功的响应,包含一个简单的 HTML 页面。

具体实现流程

  1. 构建状态行和头部字段(通常是字符串常量或小块内存)。
  2. 从磁盘读取请求的文件内容到另一个大的内存缓冲区(如通过mmapread)。
  3. 使用writev将头部和体一次性写入套接字。

带注释的完整代码

http_server_writev.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/uio.h> // For struct iovec#define PORT 8080
#define RESPONSE_HEADER "HTTP/1.1 200 OK\r\n"         \"Server: MyServer\r\n"        \"Content-Type: text/html\r\n" \"Connection: close\r\n"       \"\r\n" // The empty line ending headers
#define RESPONSE_BODY "<html><body><h1>Hello, writev!</h1></body></html>\r\n"int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);// 1. Create socket file descriptorif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 2. Set socket optionsif (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 3. Bind the socket to the network address and portif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 4. Listen for incoming connectionsif (listen(server_fd, 3) < 0) {perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 5. Accept an incoming connectionif ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 6. Prepare the data to be sent using writev// Our response consists of two parts: the header and the body.// We define an array of iovec structures to describe these two buffers.char header_buf[] = RESPONSE_HEADER; // Buffer for header (on stack)char body_buf[] = RESPONSE_BODY;     // Buffer for body (on stack)struct iovec iov[2]; // We have two disjoint buffers// First buffer: HTTP headeriov[0].iov_base = header_buf;iov[0].iov_len = strlen(header_buf);// Second buffer: HTTP response bodyiov[1].iov_base = body_buf;iov[1].iov_len = strlen(body_buf);// 7. Use writev to send both buffers in one system callssize_t bytes_sent = writev(new_socket, iov, 2);if (bytes_sent < 0) {perror("writev failed");} else {printf("Successfully sent %zd bytes of response.\n", bytes_sent);}// 8. Close the client socket and server socketclose(new_socket);close(server_fd);return 0;
}

Makefile

CC=gcc
CFLAGS=-Wallall: http_serverhttp_server: http_server_writev.c$(CC) $(CFLAGS) -o $@ $<clean:rm -f http_server

编译与运行

  1. 保存代码到文件,并运行 make 进行编译。
  2. 运行生成的可执行文件:./http_server
  3. 使用浏览器访问 http://localhost:8080 或使用 curl 命令:curl http://localhost:8080
  4. 服务器终端将打印发送的字节数,客户端将收到完整的 HTTP 响应。
3.2 实例二:高性能日志记录系统

日志消息通常包含固定的元数据(时间戳、日志级别、文件名)和可变的消息内容。使用writev可以避免将这两部分拼接成一个字符串,从而提升日志写入性能。

应用场景:一个服务程序需要将格式化的日志行写入文件或标准错误。

具体实现流程

  1. 获取当前时间,格式化成字符串(第一部分缓冲区)。
  2. 定义固定的日志级别和项目标识符字符串(第二、三部分缓冲区)。
  3. 用户提供的可变消息内容(第四部分缓冲区)。
  4. 换行符(第五部分缓冲区)。
  5. 使用writev将所有部分一次性写入日志文件描述符。

带注释的完整代码

logger_writev.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/uio.h> // For struct iovecvoid log_message(int fd, const char *level, const char *filename, const char *message) {// 1. Get current time and format ittime_t now = time(NULL);struct tm *tm_info = localtime(&now);char time_buffer[20]; // Buffer for timestampstrftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", tm_info);// 2. Define other fixed parts of the log messagechar fixed_part[] = " [MyApp] "; // Fixed project identifierchar newline = '\n';// 3. Prepare the iovec array for all 5 parts of our log line.// Format: [Timestamp] [Level] [MyApp] [Filename] Message\n// Example: "2023-10-27 10:11:12 [ERROR] [MyApp] main.c: Connection failed\n"struct iovec iov[6]; // We need 6 segments// Segment 0: Timestampiov[0].iov_base = time_buffer;iov[0].iov_len = strlen(time_buffer);// Segment 1: Space and Leveliov[1].iov_base = " ";iov[1].iov_len = 1;iov[2].iov_base = (void *)level; // Cast away const, we know we won't modify itiov[2].iov_len = strlen(level);// Segment 3: Fixed project identifieriov[3].iov_base = fixed_part;iov[3].iov_len = strlen(fixed_part);// Segment 4: Filename and message// We can combine these into one segment if we want, but we'll use two for demonstration.iov[4].iov_base = (void *)filename;iov[4].iov_len = strlen(filename);iov[5].iov_base = ": ";iov[5].iov_len = 2;// Note: We need a 7th segment for the actual message and a 8th for the newline.// This shows the flexibility, but also the complexity of many segments.// Let's re-design to a simpler 5-segment approach.// --- Re-designed approach with 5 segments ---// We'll let the message include the filename and colon.// This is less flexible but clearer for the example.// A real logger would use a more sophisticated approach, perhaps with a loop to build the iov array.struct iovec final_iov[5];// Segment 0: Timestampfinal_iov[0].iov_base = time_buffer;final_iov[0].iov_len = strlen(time_buffer);// Segment 1: " LEVEL [MyApp] filename: "// We need to create a format string. For simplicity, we snprintf a buffer.// This shows a hybrid approach: sometimes a temp buffer for complex formatting is simpler.char prefix_buffer[256];snprintf(prefix_buffer, sizeof(prefix_buffer), " %s [MyApp] %s: ", level, filename);final_iov[1].iov_base = prefix_buffer;final_iov[1].iov_len = strlen(prefix_buffer);// Segment 2: User messagefinal_iov[2].iov_base = (void *)message;final_iov[2].iov_len = strlen(message);// Segment 3: Newlinefinal_iov[3].iov_base = &newline;final_iov[3].iov_len = 1;// 4. Write the complete log line with one writev call to stderr (fd=2)ssize_t n = writev(fd, final_iov, 4); // 4 segmentsif (n == -1) {perror("writev logging failed"); // Log failure... but where to?}
}int main() {// Log a few messages to stderr (file descriptor 2)log_message(STDERR_FILENO, "INFO", __FILE__, "Server started successfully.");log_message(STDERR_FILENO, "ERROR", __FILE__, "Failed to connect to database.");// Also log to a fileFILE *logfile = fopen("app.log", "a");if (logfile) {log_message(fileno(logfile), "WARN", __FILE__, "Disk space is low.");fclose(logfile);}return 0;
}

说明:这个日志示例比 HTTP 示例更复杂,因为它展示了动态构建 iovec 数组的常见模式。有时,为了生成一个格式化的前缀,使用 snprintf 到一个临时小缓冲区仍然是最高效和清晰的方法,然后再用 writev 将这个前缀和主体消息一起发送。这仍然比将整个日志行拼接成一个字符串要节省一次大的内存拷贝。

编译与运行

  1. 编译:gcc -Wall -o logger logger_writev.c
  2. 运行:./logger
  3. 输出将会显示在终端(标准错误),同时也会写入到 app.log 文件中。

4. 交互性内容解析:writev 与网络交互

writev 用于套接字(Socket)时,它的行为与内核的网络协议栈(尤其是 TCP)深度交互。

4.1 内核处理流程与报文生成
  1. 用户空间调用:应用程序调用 writev(sockfd, iov, iovcnt)
  2. 上下文切换:CPU 从用户态切换到内核态。
  3. 内核空间处理
    • 内核验证参数和文件描述符的有效性。
    • 内核遍历 iov 数组,确保所有描述的内存区域对当前进程都是可读的。
    • 数据仍然位于用户空间的内存页中。
  4. 协议栈处理(TCP为例)
    • 数据从用户缓冲区被“收集”到内核的套接字发送缓冲区(Socket Send Buffer)。这个过程可能涉及页映射而非直接拷贝(Zero-copy 技术的目标之一,但并非所有情况都能实现)。
    • TCP 协议处理数据:将发送缓冲区中的字节流分割成适合网络传输的报文段(MSS)。writev 的边界信息在此时通常会丢失。TCP 是字节流协议,它不保留消息边界。writev 中的多块数据会被TCP视为一个连续的字节流。
    • 为每个报文段添加 TCP 头(序列号、确认号等)。
    • 交给 IP 层添加 IP 头,再交给数据链路层。
  5. 报文发送:网卡驱动程序将完整的以太网帧发送到网络。
  6. 返回用户空间writev 系统调用返回成功发送的字节总数,上下文切换回用户态。

重要注意点:虽然 writev 在用户层面是“分散”的,但在网络层面,这些数据很可能被整合到一个或多个TCP报文段中发送。writev 的原子性体现在套接字层面(接收方的一次read可能读到所有数据),而不是网络报文层面

4.2 时序图

下面的时序图描绘了客户端使用 writev 发送HTTP请求和服务端使用 writev 发送HTTP响应的完整交互过程,以及内核内部的数据流。

ClientClient KernelSocket BufferNetworkServer KernelSocket BufferServerHTTP Request Phasewritev(sockfd, iov_req, 2)(Header + Body)Kernel gathers data fromuser space iov buffersTCP Packet(s)(Stream of bytes)TCP Packet(s)read(...)(Returns all request data)HTTP Response Phasewritev(sockfd, iov_resp, 2)(Header + Body)Kernel gathers response dataTCP Packet(s)(Stream of bytes)TCP Packet(s)read(...)(Returns all response data)ClientClient KernelSocket BufferNetworkServer KernelSocket BufferServer
  • 关键交互writev 的调用发生在用户空间(Client/Server),数据被“聚集”到内核的套接字缓冲区。之后,内核协议栈独立地将缓冲区中的数据打包成 TCP 报文并通过网络发送。接收方的内核将报文数据重组到它的接收缓冲区,用户空间的 read 调用再从该缓冲区中读取数据。writev 的多缓冲区特性对网络对端是透明的。

5. 总结与对比

为了更清晰地理解 writev,下表将其与传统方法进行对比:

特性多次 write 调用用户缓冲区 + 单次 writewritev
系统调用次数多 (N次)少 (1次)少 (1次)
内存拷贝次数无 (0次)多 (N次 memcpy)无/少 (0次,内核处理)
CPU开销高 (上下文切换)中 (内存拷贝)
内存开销高 (临时缓冲区)
代码复杂性中高 (缓冲区管理)中 (需管理iovec)
原子性保证有 (管道/套接字)
适用场景简单程序数据需预处理高性能服务器,多块数据IO
选型建议:
  • 使用 writev:当你需要将多块分散在内存中的数据一次性写入文件或套接字时,尤其是在性能敏感的网络服务器中(如HTTP服务器、RPC框架、数据库)。
  • 使用单次 write:当你的数据已经存储在一块连续的内存中时。这是最简单直接的方式。
  • 使用多次 write:当数据块产生的时机不同,或者逻辑上就需要分多次发送,并且性能不是首要考虑因素时。

writev 是构建高性能、高吞吐量 I/O 密集型应用的重要工具之一,深刻理解其原理和适用场景是现代系统程序员的基本素养。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/96130.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/96130.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/96130.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解 Android targetSdkVersion:从 Google Play 政策到依赖冲突

深入理解 Android targetSdkVersion&#xff1a;从 Google Play 政策到依赖冲突 作为 Android 开发者&#xff0c;你很可能在 Android Studio 中见过这条提示&#xff1a;Google Play requires that apps target API level 33 or higher。它像一个尽职的提醒者&#xff0c;时常…

灰匣(GrayBox)1.0.0 发布【提升系统权限APP】

灰匣是一个提升系统权限的工具&#xff0c;可以配合Root、三方软件&#xff08;Shizuku&#xff09;以及【设备管理员】&#xff08;设备所有者&#xff09;实现一些高级功能及底层接口&#xff0c;可以自动隔离&#xff08;冻结/禁用&#xff09;不必要的应用&#xff0c;如某…

PAT 1104 Sum of Number Segments

这一题的大意就是找一个数组中的所有子数组&#xff0c;它们的累加和为多少&#xff0c; 题目上给出的数据范围是O(n^5)那么只能遍历一次&#xff0c;不能用暴力的方法求出。 看到这一题我有两个思路&#xff1a; 1.试图用双指针和滑动窗口来把O&#xff08;n^2)的时间复杂度降…

[万字长文]AJAX入门-常用请求方法和数据提交、HTTP协议-报文、接口文档、案例实战

本系列可作为前端学习系列的笔记&#xff0c;代码的运行环境是在VS code中&#xff0c;小编会将代码复制下来&#xff0c;大家复制下来就可以练习了&#xff0c;方便大家学习。 HTML、CSS、JavaScript系列文章 已经收录在前端专栏&#xff0c;有需要的宝宝们可以点击前端专栏查…

Codesy中的UDP发送信息

Codesy UDP通讯 概述 CAA Net Base Services UDP通讯的建立 发送UDP 状态控制 效果 概述 Codesys中默认安装的通讯支持很多,不安装其他的软件也可以实现TCP通讯。但是,在使用UDP通讯时,因为我们的PLC有两个网卡,一般我们把第一个网口做编程和HMI用,把的个网口做外部通讯,…

神经网络之深入理解偏置

&#x1f50d; 1. 表达能力&#xff1a;无偏模型不能表示全体函数族 ✔ 有偏线性变换&#xff1a; yWxb&#xff08;仿射变换&#xff09; y Wx b \quad \text{&#xff08;仿射变换&#xff09;} yWxb&#xff08;仿射变换&#xff09; 能表示任意线性函数 平移是仿射空间的…

小白必看:AI智能体零基础搭建全攻略!

写在前面&#xff1a;别怕&#xff0c;真的不需要技术背景&#xff01; 你是不是经常听到"AI智能体"、"大模型"这些高大上的词&#xff0c;总觉得那是技术大牛的专利&#xff1f;别担心&#xff0c;这篇教程就是为你准备的&#xff01;我们将用最通俗的语…

React state在setInterval里未获取最新值的问题

目录 一、问题描述 二、解决方案 方案一&#xff0c;使用函数式更新 方案二&#xff0c;使用 useRef 保存最新值 一、问题描述 在 React 中&#xff0c;当在 setInterval或setTimeout 中使用 setState 时&#xff0c;经常会遇到状态不是最新值的问题。这是因为闭包导致的&a…

x86 架构 Docker 镜像迁移至 ARM 环境的详细指南

目录 一、问题背景与分析 二、解决步骤 &#xff08;一&#xff09;检查 docker-compose 版本 &#xff08;二&#xff09;升级 docker-compose 1. 对于 Linux 系统 2. 对于 Windows 系统 &#xff08;三&#xff09;验证升级 &#xff08;四&#xff09;重新运行 dock…

零代码部署工业数据平台:TRAE + TDengine IDMP 实践

对于编程初学者来说&#xff0c;软件开发流程中的开发环境配置、安装异常或报错往往需要花费大量时间查阅资料和反复试错&#xff0c;才能正常安装和启动某些软件工具。现在&#xff0c;在 TRAE 的帮助下&#xff0c;即使完全没有接触过编程&#xff0c;也能通过自然语言直接表…

史上最全Flink面试题(完整版)

1、简单介绍一下 FlinkFlink 是一个框架和分布式处理引擎&#xff0c;用于对无界和有界数据流进行有状态计算。并且 Flink 提供了数据分布、容错机制以及资源管理等核心功能。Flink提供了诸多高抽象层的API以便用户编写分布式任务&#xff1a;DataSet API&#xff0c; 对静态数…

C# .NET中使用log4Net日志框架指南

C# .NET中使用log4Net日志框架指南 log4Net是Apache基金会开发的一款高效、灵活的日志记录框架&#xff0c;广泛应用于.NET生态系统中。它支持多种日志输出目标&#xff08;如文件、数据库、控制台&#xff09;&#xff0c;并提供细粒度的日志级别控制&#xff0c;帮助开发者监…

每日算法刷题Day68:9.10:leetcode 最短路6道题,用时2h30min

一. 单源最短路&#xff1a;Dijkstra 算法 1.套路 1.Dijkstra 算法介绍 (1)定义 g[i][j] 表示节点 i 到节点 j 这条边的边权。如果没有 i 到 j 的边&#xff0c;则 g[i][j]∞。 (2)定义 dis[i] 表示起点 k 到节点 i 的最短路长度&#xff0c;一开始 dis[k]0&#xff0c;其余 …

Spring Boot + Apache Tika 从文件或文件流中提取文本内容

应用效果&#xff1a;1、安装 Apache Tika 依赖pom.xml<!-- Apache Tika 从文件中提取结构化文本和元数据 --><dependency><groupId>org.apache.tika</groupId><artifactId>tika-core</artifactId><version>2.9.2</version>&l…

qqq数据结构补充

1.绪论1.存储方式顺序存储&#xff1a;逻辑相邻&#xff0c;物理相邻链式存储&#xff1a;逻辑相邻&#xff0c;物理不一定相邻2.线性表1.顺序表1.不可扩容数组写一个顺序表1.在头文件中应有#pragam once&#xff0c;防止头文件多次编译&#xff1b;如果头文件多次编译&#x…

Anaconda与Jupyter 安装和使用

Anaconda内部集成了很多科学计算包&#xff0c;并且可以实现环境隔离 1. 安装Anaconda 定义&#xff1a;Anaconda是一个集成的Python发行版&#xff0c;专为数据科学、机器学习和AI开发而设计。它包含了常用的Python库、包管理工具&#xff08;Conda&#xff09;和Jupyter No…

5.后台运行设置和包设计与实现

程序的入口点(想让其后台默认.exe进程运行)也可以不通过vs设置也可以通过定义预处理设置第三种就是没有窗口的变成后台运行的了 处理client传来的数据包 第一步&#xff1a;咱们怎么设计一种包呢&#xff1f;FEFF在网络环境里面出现的概率低所以就采用这个 自己数据包截断了&am…

【开题答辩全过程】以 基于微信小程序校园综合服务平台的设计与实现为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

地级市人口集聚、经济集聚、产业集聚与绿色经济效率匹配数据(含区域经济研究相关的控制变量,Excel|shp|免费数据)

D006 地级市人口集聚、经济集聚、产业集聚与绿色经济效率匹配数据&#xff08;含区域经济研究相关的控制变量&#xff0c;Excel|shp|免费数据&#xff09;数据简介今天我们分享的数据是2004-2020年地级市人口聚集、经济聚集与绿色经济效率匹配数据&#xff0c;并对其进行可视化…

视觉SLAM第7讲:视觉里程计2(3D-2D:PnP、3D-3D:ICP)

接上文&#xff0c;视觉SLAM第7讲&#xff1a;视觉里程计1&#xff08;特征点法、2D-2D对极约束&#xff09;&#xff0c;本节主要学习3D-2D:PnP、3D-3D:ICP。 目录 7.7 3D-2D:PnP 7.7.1 直接线性变换&#xff08;DLT&#xff09; 7.7.2 P3P 1.原理 2.小结 7.7.3 最小化重…