【APUE】文件I/O(系统调用I/O)

news2024/11/26 13:46:41

目录

1、简介

2、文件描述符的本质

3、文件IO操作

3.1 open

3.2 close

3.3 read

3.4 write

3.5 lseek

4、文件IO与标准IO的区别

5、IO的效率问题

关键补充:进程的内存空间布局

代码区

常量区 

全局区 

.bss段

.data段

堆区

栈区 

6、文件共享

7、原子操作

8、程序中的重定向

9、同步

10、很牛很牛的函数

10.1 fcntl

10.2 ioctl

11、/dev/fd/ 目录


1、简介

在标准IO中,我们是通过 FILE 结构体进行读写操作,FILE 结构体贯穿始终;

在文件IO中文件描述符(fd,file descriptor)为贯穿始终的类型

文件IO操作:

  • oepn
  • close
  • read
  • write
  • lseek

标准IO的一切操作,底层都是基于文件IO操作实现的(封装)


2、文件描述符的本质

本质是数组下标!

先来看看标准IO中的 FILE

这个 FILE 对象(结构体)表征了一个通过标准 IO 打开的文件

我们在这里强调:标准IO提供直接获取指向 FILE 结构体指针的接口! 

而在文件IO中不同。虽然文件IO也会有一个结构体

与标准IO不同,文件IO的接口不会给你指向所产生结构体的指针,只会将该结构体指针存在某个数组的某个索引位置中,这个索引就是文件描述符fd 

我们之前说过,标准IO的fopen基于文件IO中的open。因此标准IO所得到的流——FILE结构体中,很容易想到里面应该有个 int 类型的 fd,用于表征文件IO所产生的结构体

因为标准IO封装了系统调用IO,默认情况下有这样的一些关系:

  • 流 stdin 指向一个结构体,结构体内部的 fd 值为 0,索引到某个数组的位置 0 里面有个指针,该指针指向系统 IO 所产生的结构体 
  • 流 stdout 指向一个结构体,其内部的 fd 值为 1,索引到某个数组的位置 1 里面有个指针,该指针指向系统 IO 所产生的结构体 
  • 流 stderr 指向一个结构体,其内部的 fd 值为 2,索引到某个数组的位置 2 里面有个指针,该指针指向系统 IO 所产生的结构体 

几个注意点:

  1. 文件描述符本质上是整型数0、1、2、3、...,表示数组下标
  2. 文件描述符优先使用当前可用范围内最小的一个。比如已用的 fd 为0、1、2,则新 open 一个文件的 fd 应该为3(open产生结构体,指向该结构体的指针存放在某个数组的下标3处)
  3. 存放 open 所产生结构体的指针的数组位于进程的进程空间。每个进程都有这样的数组。产生的结构体也位于进程空间,不同进程空间中的结构体是不一样滴
  4. 存放 open 所产生结构体的指针的数组大小可通过 ulimit -a 查看,即为最大的打开文件数量

思考:

问题1:利用 open 打开一个文件两次,会怎么样?

第一次调用 open,产生一个结构体用来表征某个具有唯一inode的文件

第二次调用 open,又产生另一个结构体用来表征该文件

因为不同的结构体指针存在于数组的不同索引处

因此出现不同 fd 值代表同一个文件的情况是有可能出现的

问题2:如果数组不同索引处的指针指向了相同结构体,会怎么样 

试想:如果我们对某个 fd 调用 close,如果没有特殊的机制,可以遇见的是系统会释放结构体所在内存空间。这样的话,数组另一个 fd 下标内的指针就指向了一个已经被释放的空间!即出现了悬空指针的情况

因此,文件IO产生的结构体中有一个成员用于记录引用计数,用来记录有多少指针指向它。当结构体中的引用计数变为 0,其所在空间才会被释放,这样就不会出现悬空指针的情况了


3、文件IO操作

3.1 open

使用手册,man 2 open

系统调用 I/O 在 man 手册第二部分 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);    
// 若flags设置了O_CREAT标志,则需要用mode设置权限

功能: 打开(可能创建)文件

  • pathname — 待打开的文件或设备的名字
  • 返回一个文件描述符。失败则返回 -1,并设置 errno
  • flags — 其实就是位图,每一位用 0 或 1 来表示包含/不包含某种属性 

再看一下 flags 参数 man 手册中的介绍:

        参数标志必须包括以下访问模式之一:O_RDONLY、O_WRONLY 或 O_RDWR。它们分别要求以只读、只写或读/写方式打开文件。

        此外,还可以按照二进制或的方式,将零或多个文件创建标志和文件状态标志包括进参数标志。

        文件创建标志包括 O_CLOEXEC、O_CREAT、O_DIRECTORY、O_EXCL、O_NOCTTY、O_NOFOLLOW、O_TMPFILE 和 O_TRUNC。

        文件状态标志包括上述标志外的所有标志。

        这两组标志的区别在于:文件创建标志会影响打开操作本身的语义,而文件状态标志则会影响后续 I/O 操作的语义。

必须部分可选部分
O_RDONLY:以只读方式打开O_CREAT:按照参数 mode 给决定的权限设置创建文件
O_WRONLY:以只写方式打开

O_EXCL:与 O_CREAT 一起使用,确保创建出文件,避免两个程序同时创建同一个文件,如文件存在则 open 调用失败

O_RDWR:以读写方式打开O_APPEND:把写入数据追加在文件的末尾
O_TRUNC:把文件长度设置为0,丢弃原有内容
O_NONBLOCK:以非阻塞模式打开文件

 (上面只介绍了常用的标志)

  •  mode — 主要是搭配 O_CREAT 使用,这个参数规定了用户、用户所在组和其他人对所创建文件的文件操作权限

关于权限:

权限公式:mode & ~umask

LINUX 默认的 umask 值:八进制数 0022 (注意第一个0只是说明这个数是八进制,是个符号) 

则:

LINUX 默认创建文件的实际权限:0666 & ~umask = 0666 & ~0022 = 0644

LINUX 默认创建文件夹的实际权限:0777 & ~umask = 0777 & ~0022 = 0755

使用 open 系统调用创建文件的实际权限:mode & ~umask 

验证如下: 


open 函数引发的思考:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);   

看看这个形式,像不是像  C++ 中的函数重载?

但是 LINUX C 没有函数重载!

那是怎么实现的呢?答:变参函数

变参数函数的原型声明为:

Vtype VAFunction(type arg1, type arg2, ...);

变参函数可以接受不同类型的参数,也可以接受不同个数的参数。

参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用 ... 表示。固定参数和可选参数共同构成一个函数的参数列表

以 printf 为例,它就是一个变参函数:

int printf(const char *fmt, ...){    
    int i;    
    int len;    
    va_list args; /* va_list 即 char * */
    
    va_start(args, fmt);    
    /* 内部使用了 va_arg() */
    len = vsprintf(g_PCOutBuf,fmt,args);
    
    va_end(args);    
    for (i = 0; i < strlen(g_PCOutBuf); i++)
    {
        putc(g_PCOutBuf[i]);
    }    
    return len;
}

若是在 C++ 下,区分重载和变参函数,只需要调用时多传入几个参数就行。 


3.2 close

#include <unistd.h>

int close(int fd);

功能:关闭一个文件描述符(使数组某个下标重新可被使用)

  • fd — 待关闭的文件描述符
  • 成功返回 0 ;出错返回 -1,并设置 errno 

3.3 read

man 2 read

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
// read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

功能:读一个文件描述符 

  • fd — 从哪个文件描述符读
  • buf — 将读到的字符放到哪个位置去
  • count — 限制本次读取最多读多少个字符(字节,一个字符等于一个字节)
  • 成功则返回实际读取到的字符个数;失败返回 -1,并设置 errno

一个注意点:调用 read 后,文件位置指针会前进读取到的字符个数步


3.4 write

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

// write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.

功能:向一个文件描述符写内容

  • fd — 向哪个文件描述符写
  • buf — 所写内容的来源,将位于 buf 的字符写进 fd
  • count — 限制本次写入最多写多少个字符
  • 成功则返回实际写入的字符个数;失败返回 -1,并设置 errno

这里引入一个拓展点, 一个写代码时候的关键技术:坚持写够 len 字节 

// 假如我们希望保证能够写入 len 个字节
// 从 buf 中获取字符,写入 fd 所表征的文件或设备

pos = 0;

while(len > 0)
{
    ret = write(fd, buf + pos, len);    // 本次已写入的字节数
    
    if (ret < 0)
    {
        perror("write()");
        exit(1);
    }
    
    pos += ret;    // 更新下次继续写,所需要的数据源位置
    len -= ret;    // 更新剩余的待写入字节数

}

3.5 lseek

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

功能:设置文件位置指针

  • fd — 设置 fd 所表征的文件 
  • whence && offset — 将文件位置设置在:从 whence 给定的位置加上偏移 offset 的位置
  • 返回设置后的文件位置指针所指位置(从文件起始位置开始,并以字节为单位度量,相对起始位置的偏移)

whence 取值:

字段含义
SEEK_SET文件首个字符位置
SEEK_END

文件最后一个字符的下一个位置

也可以理解为:将整个文件中的字符看成存放在数组里的字符,则 SEEK_END 代表索引为文件大小的位置

SEEK_CUR文件位置指针的当前位置

代码示例:文件位置指针对读取的影响


4、文件IO与标准IO的区别

主要区别就是:标准IO封装了文件IO,且引入了缓冲机制

  • 标准IO写入Buffer,在适当的时候才会调用系统IO将Buffer的内容实际写入磁盘
  • 标准IO从磁盘读到Cache,在适当的时候才会调用系统IO将Cach的内容实际读取到进程空间

响应速度:文件IO更快,调用一次文件IO,就会从用户态立即陷入内核态写入磁盘

吞吐量:标准IO更大,每次系统调用IO可能作用于多个字节

文件IO不使用缓存,每次调用读写函数时,从用户态切换到内核态,对磁盘上的实际文件进行读写操作,因此响应速度快,坏处是频繁的系统调用会增加系统开销(用户态和内核态来回切换),例如调用write写入一个字符时,磁盘上的文件中就多了一个字符

标准IO使用缓存,未刷新缓冲前的多次读写时,实际上操作的是内存上的缓冲区,与磁盘上的实际文件无关,直到刷新缓冲时,才调用一次文件IO,从用户态切换到内核态,对磁盘上的实际文件进行操作

优先选用标准IO

举例:办理合同盖章业务

小F拿到一份合同后,可能立即跑去盖章。这样跑一趟只能盖一个章

小Y拿到一份合同后,先把这个合同攒着,等合同攒到一定数量了,才拿着一沓合同拿去盖章。这样跑一趟就能一次性盖多个章了

显然优先选用小Y的方法,更高效


总结一下,我们在操作 FILE 结构体指针时,使用的是标准 IO;在操作文件描述符时,使用的是系统调用 IO。其实这两种 IO 是可以相互转换的

相互转换:(需要结合文件描述符的本质介绍的那张图)

#include <stdio.h>

int fileno(FILE *stream);

获取并返回一个 FILE 对象中的文件描述符,这样之前是通过标准 IO 操作 FILE 结构体指针,现在可以通过系统调用 IO 操作文件描述符了

#include <stdio.h>

FILE *fdopen(int fd, const char *mode);

将文件描述符与一个 FILE 对象关联并返回,这样之前是通过系统调用 IO 操作文件描述符,现在可以通过标准 IO 操作 FILE 结构体指针了


注意:两种 IO 能够相互转换,但是不能混用! 

也就是说对同一个文件,不要混用两种IO,否则容易发生错误

原因:标准 IO 所使用的 FILE 结构体中的文件位置指针(pos)与文件 IO 所采用的结构体中的文件位置指针基本上不一样

FILE *fp;
// 连续写入两个字符
fputc(fp); // 导致 pos++
fputc(fp); // 导致 pos++
 

但是,文件 IO 所关注的结构体中的 pos 并未加2;只有刷新缓冲区时,该 pos 才会加 2

代码示例:

putchar 是标准 IO,write 是文件 IO,打印结果如下: 

怎么回事呢?

可以通过 strace 追踪程序运行过程,能够显示所有由用户空间程序发出的系统调用

strace ./a.out 

解析:遇到文件 IO 则立即调用 write 写入磁盘,遇到标准 IO,则需要等待缓冲区刷新的时机,这里是遇到了换行符进行了刷新,将 3 个 a 字符和换行符输出到终端上。

如果想按照代码编写顺序打印,可以手动强制刷新


5、IO的效率问题

我们希望测试: 如下实现拷贝文件的代码,BUFSIZE 设置为多少,程序运行效率最高

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFSIZE 1024 // 缓冲区大小

int main(int argc, char **argv) {
    // 源文件和目标文件的文件描述符
    int sfd, dfd;
    // 读写缓冲
    char buf[BUFSIZE];
    // len:读文件的返回字节数
    // ret:写文件的返回字节数
    // pos:写文件的当前位置
    int len, ret, pos;

    if(argc < 3) {
        fprintf(stderr, "Usage...\n");
        exit(1);
    }
	
    // 以只读方式打开文件,打开文件失败
    if((sfd = open(argv[1], O_RDONLY)) < 0) {
        perror("open()");
        exit(1);
    }
	
    // 以只读方式打开文件,有则清空,无则创建
    // 打开文件失败
    if((dfd = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
        close(sfd);
        perror("open()");
        exit(1);
    }

    while(1) {
        if((len = read(sfd, buf, BUFSIZE)) < 0) {
            perror("read()");
            break;
        }
		
        // 读完文件
        if(len == 0)
            break;

        pos = 0;
        // 防止读到的字节没有完全写入文件
        // 保证读多少,就写多少
        while(len > 0) {
            if((ret = write(dfd, buf + pos, len)) < 0) {
                perror("write()");
                exit(1);
            }
            pos += ret;
            len -= ret;
        }
    }
	// 关闭文件描述符
    close(dfd);
    close(sfd);

    exit(0);
}

我们需要如下的测试进程运行时间的方法

可以在命令前加上指令 time,从而获得进程运行所需要的时间

字段含义
real总时间=用户层消耗时间、内核层消耗时间及调度等待的时间之和
user用户层面消耗的时间
sys内核层面消耗的时间

对于使用程序的用户,关注的是 real;而对于程序员,关注的是 user + sys,因为操作系统的调度策略不是由程序员的意志所决定的


关键补充:进程的内存空间布局

内存地址是一个编号,通常由16进制表示,它代表一个内存空间。在计算机中存储器的容量是以字节为基本单位的,也就是说一个内存地址代表一个字节(8bit)的存储空间,即按字节寻址。

假设一个int类型的变量x占用4个字节,则会占用4个连续的内存空间,x的内存地址则为第一个内存空间的地址。

对于32位操作系统,内存地址长度为32位,则可以表示2的32次方个内存空间(可寻址空间),即4GB;

计算:2^32 * 1B = 2^32B = 2^22 KB = 2^12 MB = 2^2 GB = 4GB

对于64位操作系统,内存地址长度为64位,则可以表示2的64次方个内存空间(16777216TB);但实际上,主板和CPU的限制导致一般的电脑所支持的内存最大只有16GB而已。

C程序(例如a.out)运行时会被加载入内存中,每个进程所占用的物理内存可抽象为该进程的地址空间。那么这块空间由哪几部分构成呢

代码区

存放代码的

常量区 

  • 存放生命周期等同于程序运行周期的只读量
  • 字符串、数字等常量

全局区 

存放生命周期等同于程序运行周期的可读写变量

.bss段

  • 未被人为初始化的全局变量及静态变量存放在.bss段
  • .bss段不占用可执行文件空间,其内容由操作系统初始化

.data段

  • 已人为初始化的全局变量及静态变量存放在.data段
  • .data段占用可执行文件空间,其内容由程序初始化

堆区

  • 堆区变量由程序员分配内存和释放
  • 堆区按内存地址由低到高方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大

栈区 

  • 用于存放生命周期被限定在块中的局部变量
  • 栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大

6、文件共享

含义:多个任务共同操作一个文件或者协同完成任务

一个应用:如何写程序删除一个文件的第十行? 

一种朴素的想法

 如图所示,从第 11 行开始,不断读取下一行的内容,写入上一行

别忘了调用 read/write 后,文件位置指针会随之前进 

上一步完毕后,会导致多出一行,再利用下述函数将文件的最后一行删除即可

#include <unistd.h>
#include <sys/types.h>

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);

// The truncate() and ftruncate() functions cause the regular file 
// named by path or referenced by fd to be truncated to a size of precisely length bytes

path — 用于指定文件路径

fd — 文件描述符,用于表征某已打开的文件

length — 将文件截断至 length 字节长度


7、原子操作

原子:不可分割的最小单位

原子操作:不可分割的操作

原子操作的作用:解决竞争和冲突 

例如:tmpname 存在并发问题,就是因为操作不是原子的


8、程序中的重定向

重定向:本来操作的是文件A,重定向后操作的是文件B

本质:文件描述符的复制(数组中所存内容的复制) 

回顾一下,文件描述符的本质是数组下标数组不同下标位置内所存放指针指向的结构体代表了不同的用来表征文件的结构体。 

#include <unistd.h>

int dup(int oldfd);    // 将oldfd索引位置中的内容复制到最小可用索引位置
int dup2(int oldfd, int newfd);    // 将oldfd索引位置中的内容复制到newfd位置处

几个关键点:

  • 复制的是数组 fd 索引中的内容:即指向结构体的指针
  • dup2 分两种情况:如果 newfd 已经打开,则先将其关闭(可以理解为先把 newfd 指向位置中的内容清空),再将 oldfd 中所存的结构体指针拷贝过去;如果 newfd 等于 oldfd,则什么也不做
  • 调用成功返回复制到的目标位置索引(新文件描述符);调用失败返回 -1,并设置 errno
  • 一个编程的好习惯:假装自己没有在 main 函数编程。这样,假如重定向了默认打开的文件描述符,最好在退出函数的时候恢复到重定向之前的状态

例:将 puts 重定向到一个文件中 

方法一:朴素方法 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define FNAME "/tmp/out"

int main(void) {
    int fd;
    close(1); // 关闭stdout,使描述符1空闲
    if((fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {    // 此时文件描述符1(数组下标1中所存的结构体指针)指代的是打开的文件
        perror("open()");
        exit(1);
    }

    puts("Hello World");    // puts将输出写到数组下标为1中所存的结构体指针所指代的文件

    exit(0);
}

方法二:利用 dup

#define FNAME "/tmp/out"

int main(void) {
    int fd;

    if((fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
        perror("open()");
        exit(1);
    }
    // 关闭stdout
    close(1);
    // 复制fd,让其占据1的描述符
    dup(fd);
    // 关闭fd
    close(fd);

    puts("Hello World");

    exit(0);
}

方法三:利用 dup2

#define FNAME "/tmp/out"

int main(void) {
    int fd;

    if((fd = open(FNAME, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) {
        perror("open()");
        exit(1);
    }

    // 如果fd = 1,则什么也不做,返回fd
    // 如果fd != 1,则关闭1指向的结构体,再打开1,指向fd的结构体,返回1
    dup2(fd, 1);

    if(fd != 1) {
        close(fd);
    }    // 如果有两个文件描述符位置所存的指针指向同一个结构体,则close一个

    puts("Hello World");

    exit(0);
}

9、同步

暂时不详细解释

man 2 sync 

man 2 fsync

man 2 fdatasync

补充:

  • 文件的数据:文件中的有效内容
  • 文件的亚数据:文件的最后修改时间、属性等等

10、很牛很牛的函数

10.1 fcntl

功能:操纵文件描述符

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

int fcntl(int fd, int cmd, ... /* arg */ );

返回值:若成功,则依赖于 cmd,若失败,则返回 -1

函数功能(部分):

  • 复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC
  • 获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD
  • 获取/设置文件状态标志(cmd=F_GETFL或F_SETFL
  • 获取/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN
  • 获取/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW
  • ......

不同的 cmd,代表对文件描述符不同的操纵选项。文件描述符相关操作函数几乎都对该函数进行了一定程度的封装

10.2 ioctl

功能:控制设备

将所有设备看成文件,会很方便,方便了绝大多数程序员,但是一切皆文件的设计理念无法对设备提供更精细的控制

ioctl 是和设备开发相关的函数,不符合“一切皆文件”的设计思路,使用 ioctl 控制不同设备,使用方式会有较大差异


11、/dev/fd/ 目录

是一个虚目录,显示的是当前进程的文件描述符信息

对于每个进程,内核都提供有一个特殊的虚拟目录 /dev/fd/,该目录中包含 "/dev/fd/n" 形式的文件名,其中 n 是与进程中打开文件描述符相对应的编号。也就是说,/dev/fd/0 就对应于进程的标志输入

打开 /dev/fd/ 目录中的一个文件等同于复制对应的文件描述符,所以下面两行代码是等价的:

fd = open("/dev/fd/1", O_WRONLY);
fd = dup(1);

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

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

相关文章

详解【异质图卷积网络 RGCN】回顾:图神经网络问题的三大处理步骤 | 从起源说起,RGCN核心公式解释,两种降低模型参数量/优化的方式,附核心代码实现讲解

书上说了,天下没有不散的宴席,但你别怕,书上还说了,人生何处不相逢。 【纯手工】优质讲解,码字不易,写作不易,走过路过,大家点个赞呗! 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP…

*p 和p区别

*p 和 &p 是在C语言中用于处理指针的两种不同操作。 *p: * 是解引用运算符&#xff0c;用于访问指针所指向的内存地址上的值。如果 p 是一个指针变量&#xff0c;*p 就是该指针所指向的值。举例&#xff1a;如果有 int *p&#xff0c;它是一个指向整数的指针&#xff0c;那…

机器学习西瓜书+南瓜书吃瓜教程第三章学习笔记

本次学习为周老师的机器学习西瓜书谢老师南瓜书Datawhale视频 视频地址 下面为本人的学习笔记&#xff0c;最近很忙还没学多少&#xff0c;之后补&#xff01;&#xff01;&#xff01; u1s1&#xff0c;边看视频边自己手推一遍真的清楚很多&#xff0c;强烈推荐自己手推虽然花…

BUU [HCTF 2018]Hideandseek

BUU [HCTF 2018]Hideandseek 考点&#xff1a; 软连接读取任意文件Flask伪造session/proc/self/environ文件获取当前进程的环境变量列表random.seed()生成的伪随机数种子MAC地址(存放在/sys/class/net/eth0/address文件) 国赛的时候遇见过软连接&#xff0c;这次再来学习一下…

加州法案提议在州一级监管人工智能

加州高级立法者将于周三向州参议院提出一项新的人工智能 (AI) 法案&#xff0c;加强国家和全球监管快速发展技术的努力。 加州参议员斯科特表示&#xff0c;尽管国会多次尝试起草人工智能立法&#xff0c;但加州——硅谷的所在地&#xff0c;世界上大多数顶级人工智能公司都位于…

专治机器学习面试:机器学习各个算法的优缺点!

今天有朋友聊起来&#xff0c;机器学习算法繁多&#xff0c;各个算法有各个算法的特点。以及在不同场景下&#xff0c;不同算法模型能够发挥各自的优点。 今天呢&#xff0c;我把常见的、常用的算法模型进行了一个大概的总结。包括其分支以及各分支的优缺点。 涉及到的算法有…

欢迎参与体素大战活动!

全新的节目即将登陆 The Sandbox 直播流&#xff0c;我们希望你能成为其中的一员&#xff01; 我们正在寻找 20 位 VoxEdit 艺术家来参与这场惊喜的直播活动&#xff0c;本次活动只需要屏幕共享即可。您将在快节奏的环境中进行创作&#xff0c;以竞争「最佳快速设计 Voxel 艺术…

Git学习笔记7

github上多人协助开发演示过程&#xff1a; 张三是项目作者。 李四是一个协同的用户。觉得项目不错&#xff0c;想增加一些功能。 clone与fork的区别&#xff1a; clone&#xff1a;任何人都可以下载作者的整个项目&#xff0c;但是非作者不能push到作者的项目里。&#xff…

【Java 基础篇】Java对象反序列化流详解

在Java编程中&#xff0c;对象序列化和反序列化是常见的操作&#xff0c;用于将对象转换为字节流以便于存储或传输&#xff0c;并从字节流中重新构建对象。本文将重点介绍对象反序列化流的用法和相关概念&#xff0c;帮助基础小白理解这一重要的主题。 什么是对象反序列化&…

javaee之黑马乐优商城4

商品规格与数据结构 下面来说一下数据库的设计与分析 其实对于spu这张表来说&#xff0c;大体设计还是比较好设计的 看一下下面这张图是一个产品的规格参数 上面主体就是一个规格参数&#xff0c;基本信息又是一个规格参数 这里就是涉及到了一个商品的具体信息&#xff0c;sku…

在时代的浪潮中实在前行!实在智能与浪潮通软全面开启战略合作

“新”潮涌动&#xff0c;浪花朵朵。近日&#xff0c;杭州实在智能科技有限公司与浪潮通用软件有限公司签署战略合作协议。双方将加快科研创新&#xff0c;扩大合作领域&#xff0c;共同开拓数智化市场&#xff0c;携手共赢。 浪潮通软平台软件与技术研究院总经理周祥国、实在智…

游戏使用脚本配置HTTP

在游戏世界中&#xff0c;使用脚本工具帮助优化游戏体验已经成为一种普遍现象。但是&#xff0c;是否需要结合代理IP来使用游戏脚本呢&#xff1f;本文将探讨游戏使用脚本时是否需要代理IP的利弊&#xff0c;并分享合规使用脚本的方法。 1. 提高账号安全性&#xff1a; - 通过…

知识图谱的搭建

知识图谱搭建最重要的核心在于对业务的理解以及对知识图谱本身的设计&#xff0c;这就类似于对于一个业务系统&#xff0c;数据库表的设计是至关重要的&#xff0c;而这种设计师根据业务及未来场景的变化预估不断探索得出的。 一个完整的知识图谱的构建包含以下几个步骤&#…

Spring 中的Aware接口有什么作用

文章目录 0.前言1.什么是Aware接口2.Aware接口的设计目的3.详解3.1. ApplicationContextAware我们举个例子来说明 3.2. BeanFactoryAware3.3. BeanNameAware3.4. ServletContextAware3.5. MessageSourceAware3.6. ResourceLoaderAware 4.参考文档 0.前言 背景&#xff1a; 最近…

Centos7完全离线环境安装Nvidia Tesla A100 40G显卡驱动(含CUDA Toolkit)和Anaconda3虚拟环境

公司一台完全离线环境的服务器刚装了Nvidia Tesla A100 40G显卡&#xff0c;自己摸索着将显卡驱动在完全离线环境下安装成功&#xff0c;这里记录一下。 一、下载Centos7适配的Nvidia Tesla A100 40G显卡驱动 在Nvidia官网下载Centos7适配的显卡驱动&#xff0c;CUDA Toolkit…

idea(第一次)启动项目,端口变成了8080

先上配置 server:port: 9569 spring:profiles:active: dev 该排查的问题也都没问题&#xff0c;重启idea也试过了&#xff0c;还是8080 解决办法&#xff1a;点击右侧的maven ,左上角的重新导入 reimport all maven projects 我又没有改动pom文件&#xff0c;居然还要点这…

玩机教程:阿里云无影云电脑怎么使用?

阿里云无影云电脑即无影云桌面&#xff0c;云电脑如何使用&#xff1f;云电脑购买后没有用户名和密码&#xff0c;先创建用户设置密码&#xff0c;才可以登录连接到云桌面。云桌面想要访问公网还需要开通互联网访问功能。阿里云百科来详细说下阿里云无影云电脑从购买、创建用户…

Android 13.0 SystemUI下拉状态栏背景增加高斯模糊背景

1.概述 在13.0的产品开发中,发现现在很多产品都是高斯模糊背景的,这种高斯模糊背景看起来效果很不错,产品开发需要SystemUI下拉状态栏背景也是高斯模糊背景,所以就要来实现下拉状态栏高斯模糊背景 2.SystemUI 下拉状态栏背景增加高斯模糊背景核心类 frameworks/base/pack…

python后端和前端通过websocket通讯发消息最小案例,十分钟看懂

前端和后端通过websocket实现发送消息案例&#xff0c;用于理解websocket&#xff0c;服务端可以主动给客户端发送消息&#xff0c;而且是长连接&#xff0c;比http效率高&#xff0c;因为http要不断地创建和销毁socket实例&#xff0c;导致压力很大。websocket一次创建&#x…

自定义类型:结构体----初学者笔记

目录 1. 结构体类型的声明 1.1 结构体类型的简单介绍和声明 1.1.1 结构的声明 1.1.2 特殊的声明 1.1.3 结构的自引用 2. 结构体变量的创建和初始化 3. 结构成员访问操作符 4. 结构体内存对⻬ 4.1 对齐规则 4.2 练习 4.2.1 练习1 4.2.2 练习2 4.3 为什么存在内存对…