[操作系统] 基础IO:系统文件I/O

在 Linux 操作系统中,文件 I/O(输入/输出)是程序与文件系统交互的基础。理解文件 I/O 的工作原理对于编写高效、可靠的程序至关重要。本文将深入探讨系统文件 I/O 的机制。


一种传递标志位的方法

在 Linux 中,文件的打开操作通常使用标志位来指定文件的访问模式。open() 系统调用用于打开文件,其原型如下:

int open(const char *pathname, int flags, mode_t mode);
  • pathname:要打开的文件路径。
  • flags:打开文件时的标志位,指定文件的访问模式和行为。
  • mode:文件权限,仅在创建新文件时使用。

常见的标志位包括:

参数必须包括以下三个访问方式之一。

- `O_RDONLY`:只读模式。
- `O_WRONLY`:只写模式。
- `O_RDWR`:读写模式。

其他的访问模式。

- `O_CREAT`:如果文件不存在,则创建文件。
- `O_TRUNC`:如果文件存在,则将其长度截断为零。
- `O_APPEND`:每次写入都追加到文件末尾。

标志位的原理:

原理就是位图。不同的访问模式位图上的标记位置不同,传参是通过或操作( | )即可得到需要访问模式的位图所有标记位置。然后再打开或操作文件时就会按照传入的访问模式进行。

文件权限mode

新创建文件的最终权限 = mode & ~umask

例如,以下代码以读写模式打开文件 example.txt,如果文件不存在则创建:

int fd = open("example.txt", O_RDWR | O_CREAT, 0666);

在此,0666 是文件的权限掩码,表示文件所有者、所属组和其他用户均具有读写权限。


hello.c 写文件

在 C 语言中,使用 open() 打开文件后,可以使用 write() 系统调用向文件写入数据。以下是一个示例:

#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        // 错误处理
        return 1;
    }

    const char *text = "Hello, Linux!";
    ssize_t bytes_written = write(fd, text, strlen(text));
    if (bytes_written == -1) {
        // 错误处理
        close(fd);
        return 1;
    }

    close(fd);
    return 0;
}

fd中写入buf,一次最多count个。

在此示例中:

  • open() 以写入模式打开文件 example.txt,如果文件不存在则创建,权限为 0666
  • write() 将字符串 "Hello, Linux!" 写入文件。
  • close() 关闭文件描述符,释放资源。

每次写入字符串不用留'\0'的位置,文件本身可以看做数组,如果中间存在'\0',则在读取文件时会造成错误。

当向文件内写入内容时,可以进行文本写入和二进制写入,两者的区别写入是语言层面的概念,系统不会关心类型,只要写入内容就会直接写入。

hello.c 读文件

读取文件的过程与写入类似,使用 read() 系统调用从文件中读取数据。示例如下:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        // 错误处理
        return 1;
    }

    char buffer[128];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read == -1) {
        // 错误处理
        close(fd);
        return 1;
    }

    buffer[bytes_read] = '\0'; // 确保字符串以 null 结尾
    printf("File content: %s\n", buffer);

    close(fd);
    return 0;
}

fd中读取,拷贝到buf中,最多读取countbytes(sizeof(buf) - 1( - 1是为了在buf的末尾存贮'\0'))。

在此示例中:

  • open() 以只读模式打开文件 example.txt
  • read() 从文件中读取数据到缓冲区 buffer
  • close() 关闭文件描述符。

open 函数返回值

区分两个概念:**系统调用****库函数**

  • fopen``fclose``fread``fwrite等都是C标准库中的函数,称之为库函数(libc)
  • open``close``read``write``lseek等属于系统提供的接口,称之为系统调用接口

通过上图可以理解库函数和系统调用之间的关系。可以认为f*系列的函数是对系统调用的封装,方便二次开发。


open() 函数的返回值是一个文件描述符(fd),用于标识打开的文件。成功时返回非负整数,失败时返回 -1,并设置 errno 以指示错误类型。常见的错误包括:

  • EACCES:权限不足。
  • ENOENT:文件不存在。
  • EINVAL:无效的标志位。

例如:

int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
    perror("Error opening file");
    return 1;
}

文件描述符 fd

文件描述符(fd)是一个非负整数,用于标识进程打开的文件。标准输入、标准输出和标准错误分别对应文件描述符 0、1 和 2。文件描述符的分配规则如下:

  • 默认情况下,标准输入、标准输出和标准错误分别占用 0、1 和 2。
  • 通过 open() 打开的文件从 3 开始分配。

所以当我们查看在程序中打开的文件的fd时发现都是3之后的,就是因为在程序运行前就有自动升层的代码在开头打开了三个标准流文件,已经占据了0,1,2。

0 & 1 & 2

  • 0:标准输入(stdin),通常对应键盘输入。
  • 1:标准输出(stdout),通常对应屏幕输出。
  • 2:标准错误(stderr),用于输出错误信息。

通过6可知

通过6中关于FILE的讲解,当向open等函数返回值fd实际上就是进程内管理文件的数组的下标。所以当传入close等函数调用时就会通过下标来找寻这个文件,然后进行文件操作。

而对于库函数来说,返回值为FILE,作为将fd包装好的结构体,在函数内部使用系统调用的时候会自行进行处理。

FILE

FILE是什么呢?

在 C 语言标准库中,FILE 是一个用于描述文件的结构体,通常由 stdio.h 提供。它提供了一种便捷的接口,让我们可以操作文件而无需直接涉及底层的文件描述符。

FILE 结构体的内部实现

FILE 结构体并不是操作系统原生的,而是由 C 标准库(如 GNU C 库)定义的,它封装了文件的元数据,并提供了缓冲机制以提高 I/O 操作的效率。虽然不同的系统和编译器可能有不同的实现,以下是 FILE 结构体的一种典型实现:

struct _iobuf {
    char *_ptr;       // 指向缓冲区的指针
    int _cnt;         // 缓冲区的剩余字节数
    char *_base;      // 缓冲区的起始位置
    int _flag;        // 文件状态标志(如是否可读、是否可写)
    int _file;        // 文件描述符
    int _charbuf;     // 读缓存区的状态
    int _bufsiz;      // 缓冲区大小
    char *_tmpfname;  // 临时文件名
};
typedef struct _iobuf FILE;

重要字段解释:

  • _ptr:指向当前缓冲区位置的指针,文件数据会存储在这里。
  • _cnt:缓冲区中剩余的可用空间字节数。
  • _base:缓冲区的起始位置。
  • _flag:存储文件的状态标志,如文件是否处于读写模式等。
  • _file:该文件对应的系统级文件描述符,这是最直接的文件标识。
  • _bufsiz:缓冲区的大小。
  • _tmpfname:如果文件是临时的,存储其文件名。

FILE 结构体内部使用缓冲机制,这使得每次文件 I/O 操作时,程序并不直接与磁盘交互,而是将数据存入内存中的缓冲区,等缓冲区满时才将数据批量写入磁盘,从而提高 I/O 性能。

缓冲机制具体本文不做解释,之后文章会讲解。


task_structfile_struct

Linux 中的进程是由 task_struct 结构体来描述的。每个进程的 task_struct 中都包含一个 *file指向一个file_struct,这个结构体管理着该进程打开的文件。

task_struct 和文件操作的联系

task_struct 结构体代表一个进程。每个进程有自己的文件描述符表,文件描述符表由一个 file_struct 来表示。file_struct 存储了进程打开的所有文件的描述符、文件指针等信息。

struct task_struct {
    ...
    struct files_struct *files;  // 文件描述符表
    ...
};
files_struct 结构体

files_struct 是与 task_struct 相关联的结构体,存储了该进程的文件描述符表(fd_table[])。它提供了一个对文件描述符的索引和文件操作的抽象管理。每个进程的 files_struct 都有一个 fd_table[] 数组,这个数组的索引即为文件描述符(fd)。

struct files_struct {
    atomic_t count;               // 引用计数,表示该文件描述符表被多少个进程共享
    struct fdtable *fdt;          // 文件描述符表(fd_table[])
    spinlock_t file_lock;         // 保护文件描述符表的锁
};
fd_table[] 数组与 file_struct

fd_table[] 是一个数组,可以被看做文件描述符表,每个元素对应一个 file 结构体,表示一个文件。文件描述符(fd)就是 fd_table[] 数组的索引值。例如,文件描述符 0 对应标准输入(stdin),文件描述符 1 对应标准输出(stdout),文件描述符 2 对应标准错误(stderr)。

struct fdtable {
    unsigned int max_fds;         // 最大文件描述符数
    struct file **fd;             // 文件描述符数组,fd[i] 为进程打开的文件
};
  • fd[i] 表示索引为 i 的文件描述符指向的文件。
  • max_fds 表示文件描述符表的最大文件描述符数。
  • 不同的fd可以打开同一个文件,引用计数来维护,形成1 : n。
file 结构体

在 Linux 中,file 结构体表示一个打开的文件。它不仅包含了文件的数据指针和操作,还包含了与文件操作相关的状态信息。file 结构体的关键部分包括:

struct file
{
    属性
    mode
    读写位置
    读写选项
    缓冲区
    操作方法
    struct file *next; // 指向下一个fd的file结构体
}
  • f_op:文件操作结构体,包含了对文件的操作方法(如读取、写入、关闭等)。
  • f_pos:文件的当前偏移量,表示文件指针的位置。
  • f_mode:文件的访问模式(如只读、只写、读写)。
  • f_count:引用计数,表示有多少进程引用了这个文件,所以真正的文件关闭指的是引用计数为0的时候
  • 文件属性存储于结构体中,文件的内容存在缓冲区中。

文件操作的实质

从文件描述符到内核实现,文件操作的核心机制依赖于 fd_array[]file_struct

文件描述符的使用流程

每当一个进程打开文件时,内核会为文件分配一个文件描述符(fd)。这个文件描述符将作为 fd_array[] 数组的索引,指向一个 file 结构体。具体的流程如下:

  1. 文件打开:进程通过 open() 系统调用请求打开一个磁盘中的文件文件。内核会分配一个新的文件描述符(fd),并在 fd_table[] 中为该进程创建一个指向该文件的 file 结构体,属性存于结构体,内容存于结构体指向的缓冲区中。

冯诺依曼体系中,CPU不直接与硬件交互,所以需要通过内存来交互,缓冲区在内存中形成。对文件内容做任何操作,都必须先把文件加载到内核对应的文件缓冲区内,从磁盘到内存的拷贝。

  1. 文件读写:通过 read()write() 系统调用,进程会通过文件描述符访问 file 结构体中的数据,并对文件进行操作。read()本质就是内核到用户空间的拷贝函数。
  2. 文件关闭:当文件操作完成时,进程通过 close() 系统调用关闭文件。内核会减少文件描述符表中 file 结构体的引用计数,若引用计数为 0,则释放该文件描述符的资源。
通过文件描述符与 file 结构体的映射

文件描述符实际上是一个索引,它将用户空间的文件 I/O 操作映射到内核空间的 file 结构体。进程每次对文件进行读写操作时,都会通过文件描述符查找对应的 file 结构体,然后通过 file 中的操作指针(f_op)调用具体的文件操作函数,如 read(), write()flush()

文件操作的效率
  • 缓冲机制:Linux 内核使用缓冲区来提升文件 I/O 的效率。文件数据首先被写入内核缓冲区,只有缓冲区满了或程序显式调用 flush 操作时,数据才会写入磁盘。这样可以减少磁盘 I/O 的频率。
  • 文件操作锁:内核使用锁来同步文件操作,确保多个进程对同一文件的访问不会引发冲突。

结论

通过深入分析 FILE 结构体、task_struct 中的 file_struct 以及 fd_array[] 数组的关系,我们能够更清晰地理解 Linux 系统中文件操作的底层机制。文件描述符作为用户空间与内核空间的桥梁,file 结构体封装了对文件的访问接口,而内核通过文件描述符表、缓冲区机制和文件操作锁等技术,保证了高效且可靠的文件 I/O 操作。

编程语言的可移植性

编程语言的可移植性指的是程序能否在不同的平台或操作系统上顺利运行。语言的设计、标准库的实现以及对底层硬件的抽象都直接影响着程序的可移植性。

C 语言的可移植性

C 语言作为一种接近硬件的低级编程语言,直接与操作系统的底层交互。由于各个操作系统有不同的系统调用,C 语言的标准库为不同平台提供了相对一致的接口,使得 C 语言具备一定的可移植性。

不过,C 语言标准库的实现也可能因操作系统而异。比如,Windows 和 Linux 都有 C 语言的实现,但它们的文件 I/O 操作部分会有所不同,Windows 可能使用 CreateFile(),而 Linux 使用 open()。为了增强 C 语言的可移植性,开发者常常通过条件编译来区分不同操作系统下的实现。

例如,在 Windows 和 Linux 上都需要实现文件操作的代码:

#ifdef _WIN32
#include <windows.h>
HANDLE hFile = CreateFile("log.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
#else
#include <fcntl.h>
#include <unistd.h>
int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
#endif

通过使用预处理指令 #ifdef#endif,程序可以根据不同操作系统选择不同的文件打开方式,从而增加跨平台的可移植性。

语言的可移植性?

除了 C 语言,其他高级编程语言(如 C++、Java、Python、Go、PHP)也通过各自的标准库和虚拟机来增强跨平台的可移植性。

  • C++:C++ 通过标准库(如 STL)提供了一套跨平台的接口,使得程序能在不同操作系统上编译和运行。然而,当涉及到直接与操作系统底层交互时,C++ 仍然需要依赖平台特定的系统调用和 API。
  • Java:Java 提供了 Java 虚拟机(JVM),使得 Java 程序可以在不同的操作系统上运行。JVM 会屏蔽底层系统的差异,使得 Java 代码具有良好的可移植性。Java 的字节码可以在任何实现了 JVM 的操作系统上运行。
  • Python:Python 通过封装了平台特定的调用接口,提供了跨平台的标准库,如 ossys 等。Python 程序员通常不需要关心底层操作系统的细节,Python 会处理这些差异。
  • Go:Go 语言内置对多平台的支持,编译器可以直接生成不同操作系统和架构的二进制文件,从而确保 Go 程序具有较高的可移植性。
  • PHP:PHP 是一种主要用于 Web 开发的语言,它通过 Web 服务器(如 Apache、Nginx)和平台无关的接口(如数据库驱动)使得 PHP 程序具有一定的可移植性。

所以语言的移植性可以总结为:语言在底层库中的使用系统调用的函数针对不同的系统会将系统调用部分更改,更换为不同操作系统的系统调用(条件编译来解决)。

如此在上层使用语言的时候不会感受到差异,因为只是使用语言的语法,底层库的差异在语言层面进行屏蔽,增加了语言的可移植性。

语言增加可移植性让更多人愿意去使用,增加市场占有率。

不可移植性的原因?

  1. 操作系统依赖:

不同的操作系统有不同的API和系统调用。例如,Linux和windows的文件操作、内存管理、线程处理等API不同。如果现在有一个程序,在编写的时候直接调用了某个操作系统特有的API,它在其他操作系统上就无法工作。必须将调用特有API更换为要在上面执行的操作系统的API才可以正常运行。

  1. 硬件依赖:

不同平台使用的编译器可能会有不同的行为,或者某些编辑器不支持某些特性。例如,C++中某些编译器特性只在特定的编译器中有效,导致代码在其他平台或编辑器中无法运行。

重定向

文件描述符的分配规则

当进程打开文件时,操作系统会分配一个最小的未使用文件描述符。例如:

int fd = open("example.txt", O_RDONLY);

如果文件描述符 3 未被占用,则 fd 将被赋值为 3。

重定向

重定向的核心原理在于操作文件描述符。文件描述符在file_struct中的数组中存放管理,通过改变文件描述符的指向,我们可以将输入或输出流重定向到文件、设备或其他流。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h> // 包含close函数的声明

int main() {
    // 关闭标准输出文件描述符1
    close(1);

    // 打开(或创建)一个名为"myfile"的文件,以只写方式打开
    // 如果文件不存在则创建,权限设置为644
    int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
    if (fd < 0) {
        // 如果打开文件失败,输出错误信息并返回1
        perror("open");
        return 1;
    }
    
    // 输出文件描述符
    printf("fd: %d\n", fd);
    
    // 刷新标准输出缓冲区,确保输出立即显示
    fflush(stdout);
    
    // 关闭文件描述符
    close(fd);
    
    // 程序正常退出
    exit(0);
}

已知文件描述符的分配规则和重定向的原理,那么通过以上代码理解。先关闭fd = 1的文件,也就是标准输出流文件。此时再打开文件时就会按照文件描述符的分配规则,将新打开的文件描述符设置为按照顺序最小的下标,也就是刚关闭fd = 1。然后当使用printf进行打印的时候,该函数默认的拷贝到的文件fd1,本来是向显示屏进行打印,实际上因为新文件的占用,将内容拷贝进行新文件中。

这就是重定向,数组的下标不变,更改文件描述符的指针指向。

使用 dup2() 系统调用

在 Linux 中,dup2() 系统调用用于复制一个文件描述符,并将其指向另一个指定的文件描述符。这对于实现输入输出的重定向非常有用。

函数原型:

int dup2(int oldfd, int newfd);
  • oldfd:现有的文件描述符。
  • newfd:目标文件描述符。

功能:

  • oldfd 指向的文件复制到 newfd
  • 如果 newfd 已经打开,则先关闭它。
  • 返回新的文件描述符 newfd,如果出错则返回 -1

示例代码:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    // 打开文件,获取文件描述符
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        perror("打开文件失败");
        return 1;
    }

    // 将标准输出重定向到文件
    if (dup2(fd, STDOUT_FILENO) == -1) {
        perror("重定向标准输出失败");
        close(fd);
        return 1;
    }

    // 关闭原始文件描述符
    close(fd);

    // 现在 printf 的输出将写入 output.txt
    printf("这行文本将被写入到 output.txt 文件中。\n");

    return 0;
}

在上述示例中:

  • 我们首先使用 open() 打开 output.txt 文件,并获取文件描述符 fd
  • 然后,使用 dup2() 将标准输出(STDOUT_FILENO)重定向到 output.txt 文件。
  • 关闭原始的文件描述符 fd
  • 之后,所有通过 printf() 输出的内容都会写入 output.txt 文件,而不是显示器。

在 minishell 中添加重定向功能

#include <iostream>
#include <ctype.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>

#define COMMAND_SIZE 1024      // 命令行最大长度
#define FORMAT "[%s@%s %s]# " // 提示符格式

// ================== 全局数据结构声明 ==================
// 1. 命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC]; // 存储解析后的命令行参数
int g_argc = 0;        // 参数个数

// 2. 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS]; // 存储环境变量
int g_envs = 0;        // 环境变量数量

// 3. 别名映射表(当前代码未完整实现)
std::unordered_map<std::string, std::string> alias_list;

// 4. 重定向相关配置
#define NONE_REDIR 0    // 无重定向
#define INPUT_REDIR 1   // 输入重定向 <
#define OUTPUT_REDIR 2  // 输出重定向 >
#define APPEND_REDIR 3 // 追加重定向 >>

int redir = NONE_REDIR;      // 记录当前重定向类型
std::string filename;        // 重定向文件名

// ================== 辅助函数声明 ==================
// [省略部分环境获取函数...]

// ================== 环境初始化 ==================
void InitEnv() {
    extern char **environ;
    // 从父进程复制环境变量到g_env数组
    for(int i = 0; environ[i]; i++) {
        g_env[i] = strdup(environ[i]); // 使用strdup复制字符串
        g_envs++;
    }
    // 设置新环境变量(示例)
    g_env[g_envs++] = strdup("HAHA=for_test");
    g_env[g_envs] = NULL;

    // 更新进程环境变量
    for(int i = 0; g_env[i]; i++) {
        putenv(g_env[i]);
    }
    environ = g_env; // 替换全局environ指针
}

// ================== 重定向处理核心函数 ==================
void TrimSpace(char cmd[], int &end) {
    // 跳过连续空白字符
    while(isspace(cmd[end])) end++;
}

void RedirCheck(char cmd[]) {
    // 开始前先将文件操作的信息初始化
    redir = NONE_REDIR;
    filename.clear();
    int start = 0;
    int end = strlen(cmd)-1;

    // 从命令末尾向前扫描寻找重定向符号
    while(end > start) {
        if(cmd[end] == '<') { // 输入重定向
            cmd[end] = '\0';  // 截断命令字符串
            end++;
            TrimSpace(cmd, end); // 跳过空格
            redir = INPUT_REDIR;
            filename = cmd + end;
            break;
        }
        else if(cmd[end] == '>') {
            // 判断是>>还是>
            if(end > 0 && cmd[end-1] == '>') { // 追加重定向
                cmd[end-1] = '\0'; // 截断命令字符串
                end++; // 移动到>后的位置
                redir = APPEND_REDIR;
            } else { // 普通输出重定向
                cmd[end] = '\0';
                end++;
                redir = OUTPUT_REDIR;
            }
            // 这时end在最后的运算符后面,然后用TrimSpace向后查找文件开头字母
            TrimSpace(cmd, end);
            filename = cmd + end; // end为文件名开头字母位置,直接cmd定位到文件名部分
            break;
        }
        else {
            end--; // 继续向前扫描
        }
    }
}

// ================== 命令执行 ==================
int Execute() {
    pid_t id = fork();
    if(id == 0) { // 子进程
        int fd = -1;
        switch(redir) {
            case INPUT_REDIR:
                fd = open(filename.c_str(), O_RDONLY);
                dup2(fd, STDIN_FILENO); // 重定向标准输入
                break;
            case OUTPUT_REDIR:
                fd = open(filename.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0666);
                dup2(fd, STDOUT_FILENO); // 重定向标准输出
                break;
            case APPEND_REDIR:
            fd = open(filename.c_str(), O_CREAT|O_WRONLY|O_APPEND, 0666);
            dup2(fd, STDOUT_FILENO);
            break;
        default: // 无重定向不做处理
            break;
        }
        
        if(fd != -1) close(fd); // 关闭不再需要的文件描述符
        
        execvp(g_argv[0], g_argv); // 执行程序
        exit(EXIT_FAILURE); // exec失败时退出
    }
    
    // 父进程等待子进程
    int status = 0;
    waitpid(id, &status, 0);
    lastcode = WEXITSTATUS(status); // 记录退出状态
    return 0;
}

// ================== 主循环 ==================
int main() {
    InitEnv(); // 初始化环境变量
    
    while(true) {
        PrintCommandPrompt(); // 打印提示符
        
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline))) continue;
        
        RedirCheck(commandline); // 重定向解析
        
        if(!CommandParse(commandline)) continue; // 命令解析
        
        if(CheckAndExecBuiltin()) continue; // 内建命令
        
        Execute(); // 执行外部命令
    }
    return 0;
}

总结

通过深入探讨文件描述符(fd)的使用,以及如何在 C 语言中实现文件的重定向功能,我们可以更好地理解 Linux 系统文件 I/O 的工作原理。掌握这些概念和技术,对于编写高效、可靠的系统级程序具有重要意义。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis-扩展功能

逻辑删除乐观锁 MyBatisPlus从入门到精通-3&#xff08;含mp代码生成器&#xff09; Db静态工具类 Spring依赖循环问题 代码生成器 MybatisPlus代码生成器 枚举处理器 我们这里用int来存储状态 需要注解&#xff0c;很不灵活 希望用枚举类来代替这个Integer 这样的话我…

ECharts 实战指南:组件封装+地图轮廓高亮 + 自定义 Tooltip+轮播+锥形柱子

大家好&#xff0c;我是一诺。今天我们将深入探讨 ECharts&#xff0c;这个功能强大的数据可视化库。 无论你是已经在使用 ECharts&#xff0c;还是正计划用它来创建一些炫酷的图表&#xff0c;这篇文章都会对你有所帮助。 我们将从渲染模式开始&#xff0c;逐步深入到如何封…

【MyBatis】_使用XML实现MyBatis

目录 1. 配置yml配置文件 1.2 配置数据库 1.3 配置xml的路径 2. xml文件中实现数据库的增删查改操作 2.1 各文件内容 2.2 编写细节 MyBatis作为一个持久层框架&#xff0c;用于进行数据库操作。 MyBatis的实现方式有两种&#xff1a;&#xff08;1&#xff09;注解&…

单链表的概念,结构和优缺点

1. 概念 链表是一种物理存储结构上非连续&#xff0c;非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 2. 单链表的结构 单链表是由一系列节点组成的线性结构&#xff0c;每个结点包含两个域。&#xff1a;数据域和指针域。 数据域用来…

PerfMonitor高效处理器性能监控与分析利器

在追求极致电脑性能的道路上&#xff0c;一款精准、高效的处理器性能监控工具无疑是每位DIY爱好者和系统管理员的必备之选。今天&#xff0c;我们为大家带来的是CPUID出品的PerfMonitor 2&#xff0c;这款绿色小巧的软件以其强大的功能和直观的界面设计&#xff0c;赢得了广大用…

【C++】基础入门(详解)

&#x1f31f; Hello&#xff0c;我是egoist2023&#xff01; &#x1f30d; 种一棵树最好是十年前&#xff0c;其次是现在&#xff01; 目录 输入&输出 缺省参数(默认参数) 函数重载 引用 概念及定义 特性及使用 const引用 与指针的关系 内联inline和nullptr in…

数据恢复-02-故障硬盘的检测

任务描述 客户报修一故障硬盘&#xff0c;据客户描述&#xff0c;由于自己所用的台式机硬盘容量过小因而想更换一块大容量硬盘。但是在拆卸的过程中不慎将硬盘滑落在地&#xff0c;尝试对电脑进行开机&#xff0c;发现无法正常进入操作系统&#xff0c;故判断可能是硬盘故障导…

04性能监控与调优篇(D5_JVM优化)

目录 一、我们为什么要对jvm做优化&#xff1f; 二、jvm的运行参数 1. 三种参数类型 1.1. 标准 1> 参数介绍 2> 实战 3> -server与-client参数 1.2. -X参数 1> 参数介绍 2> -Xint、-Xcomp、-Xmixed 1.3. -XX参数 -Xms与-Xmx参数 2. 查看jvm的运行参…

IntelliJ IDEA 接入 AI 编程助手(Copilot、DeepSeek、GPT-4o Mini)

IntelliJ IDEA 接入 AI 编程助手&#xff08;Copilot、DeepSeek、GPT-4o Mini&#xff09; &#x1f4ca; 引言 近年来&#xff0c;AI 编程助手已成为开发者的高效工具&#xff0c;它们可以加速代码编写、优化代码结构&#xff0c;并提供智能提示。本文介绍如何在 IntelliJ I…

嵌入式软件、系统、RTOS(高软23)

系列文章目录 4.2嵌入式软件、系统、RTOS 文章目录 系列文章目录前言一、嵌入式软件二、嵌入式系统三、嵌入式系统分类四、真题总结 前言 本节讲明嵌入式相关知识&#xff0c;包括软件、系统。 一、嵌入式软件 二、嵌入式系统 三、嵌入式系统分类 四、真题 总结 就是高软笔记…

spring 学习 (注解)

目录 前言 常用的注解 须知 1 Conponent注解 demo&#xff08;案例&#xff09; 2 ControllerServiceRepository demo(案例&#xff09; 3 ScopeLazyPostConstructPreDestroy demo(案例&#xff09; 4 ValueAutowiredQualifierResource demo(案例&#xff09; 5 Co…

C语言中qsort函数使用技巧

在C语言的标准库中&#xff0c; qsort 函数是一个强大的通用排序函数&#xff0c;它采用快速排序算法&#xff0c;能够高效地对各种数据类型的数组进行排序。掌握 qsort 函数的使用技巧&#xff0c;对于提升程序的效率和代码的简洁性至关重要。 一、qsort函数基本介绍 qsort 函…

python+deepseek进行个股分析

背景&#xff1a;deepseek无法获取最新的行情数据&#xff0c;需要手动喂给它 一 用python获取最新的个股数据 请参考我的另外一篇文章&#xff1a;[python获取个股的行情数据]&#xff08;稍微改造下导出数据到excel中&#xff09;(https://blog.csdn.net/weixin_43006743/…

Golang官方编程指南

文章目录 1. Golang 官方编程指南2. Golang 标准库API文档 1. Golang 官方编程指南 Golang 官方网站&#xff1a;https://go.dev/ 点击下一步&#xff0c;查看官方手册怎么用 https://tour.go-zh.org/welcome/1 手册中的内容比较简单 go语言是以包的形式化管理函数的 搜索包名…

linux常用命令大全(包括抓包、网络检测、路由等,做项目一点点总结而来!)

文章目录 常用命令**apt相关****ls**&#xff1a;**cd****cp****ls -l | grep ssh**&#xff1a;会列出当前目录中包含 “ssh” 的文件或目录的详细信息。**系统资源**linux路由相关抓包工具和命令tcpdumpwiresharktshark iperf 常用命令 通过上下方向键 ↑ ↓ 来调取过往执行过…

HCIA项目实践--RIP相关原理知识面试问题总结回答

9.4 RIP 9.4.1 补充概念 什么是邻居&#xff1f; 邻居指的是在网络拓扑结构中与某一节点&#xff08;如路由器&#xff09;直接相连的其他节点。它们之间可以直接进行通信和数据交互&#xff0c;能互相交换路由信息等&#xff0c;以实现网络中的数据转发和路径选择等功能。&am…

[c语言日寄]字符串的左旋与右旋

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

基于单片机的开关电源设计(论文+源码)

本次基于单片机的开关电源节能控制系统的设计中&#xff0c;在功能上设计如下&#xff1a; &#xff08;1&#xff09;系统输入220V&#xff1b; &#xff08;2&#xff09;系统.输出0-12V可调&#xff0c;步进0.1V; &#xff08;3&#xff09;LCD液晶显示实时电压&#xff…

日常知识点之遗留问题梳理(被问到用uml画设计模式)

好多年不接触uml了&#xff0c;有一天面试&#xff0c;让用uml画出设计模式&#xff0c; 已经对uml的概念很模糊&#xff0c;隐约记得就是用例图&#xff0c;类图之类的&#xff0c;后面确定后&#xff0c;就是类图&#xff0c;用例图&#xff0c;时序图&#xff0c;都属于uml…

索引以及索引底层数据结构

一、什么是索引&#xff1f; 索引&#xff08;index&#xff09;是数据库高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff08;B树&#xff09;&#xff0c;这些数据结构以某种方式指向真在…