Linux篇:文件管理

news2025/2/23 20:20:21

一、共识原理:
1. 文件=内容+属性,内容与属性都是数据,都要在磁盘中保存。
2. 文件分为打开的文件和没打开的文件。
3. 研究打开的文件:本质是研究进程和文件的关系,因为是进程负责打开文件。
4. 没打开的文件在存储介质(如磁盘上)存储。没有被打开的文件非常多,所以文件如何被分门别类地归置好成为了我们需要重视的问题,我们要快速地进行找到并增删查改文件。

二、研究被打开的文件:
1、文件被打开,必须先被加载到内存。而文件的属性必须先加载到内存,而内容是否加载取决于用户后续是否要将文件写入读取。
2、系统启动时默认以文件形式(文件指针/文件句柄file*)打开三个输入输出流。由此观之,一个进程可以打开多个文件。显然,操作系统内部,一定存在大量的被打开的文件!
3、那么OS(操作系统)如何管理这些被打开的文件呢?
先描述,再组织:在内核中,一个被打开的文件都必须有自己的文件打开对象(struct结构体),其中包含文件的很多属性。所有定义的数据结构对象在内核里就可以以链表的形式管理起来,从而对打开文件的管理就转变为对链表的增删查改。

4、常用接口:

①先打开一个文件试试:

#include <stdio.h>

int main()
{
        //打开文件的路径和文件名,默认在当前路径下新建一个文件。
        FILE *fp = fopen("log.txt", "w");
        if(fp == NULL){
                perror("fopen");
                return 1;
        }
        fclose(fp);
        return 0;
}


②打开文件的路径和文件名,默认在当前路径下新建一个文件。(如果打开文件不存在,则会新建一个文件。)

当前路径即为进程当前路径cwd.

那让我们实操一下寻找一下进程的当前路径吧~

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

int main()
{
        printf("Pid: %d\n", getpid());
        //打开文件的路径和文件名,默认在当前路径下新建一个文件。
        FILE *fp = fopen("log.txt", "w");
        if(fp == NULL){
                perror("fopen");
                return 1;
        }
        fclose(fp);
        sleep(1000);
        return 0;
}

如果用户更改了当前进程的cwd,就可以把文件新建到其他目录。
可以使用chdir()修改路径。

 

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

int main()
{
        chdir("/home/aaa");//更改当前路径
        printf("Pid: %d\n", getpid());
        //打开文件的路径和文件名,默认在当前路径下新建一个文件。
        FILE *fp = fopen("log.txt", "w");
        if(fp == NULL){
                perror("fopen");
                return 1;
        }
        fclose(fp);
        sleep(1000);
        return 0;
}

③ 写入(注:该接口返回值为实际写入基本单位个数,而非总大小):

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

int main()
{
        printf("Pid: %d\n", getpid());
        //打开文件的路径和文件名,默认在当前路径下新建一个文件。
        FILE *fp = fopen("log.txt", "w");
        if(fp == NULL){
                perror("fopen");
                return 1;
        }

        const char *message = "hello Linux message";    
        //strlen(message)无需+1 ,因为/0是一种不可显字符,会被vim这样的文本编辑器解释成乱码。
        fwrite(message, strlen(message), 1, fp);        

        fclose(fp);
        return 0;
}

w:清空并写入。

重定向写入等同于fopen("log.txt", "w"):

echo "hello Linux" > log.txt

a:追加写入。

注:在c标准库里,fopen在语言层为用户malloc(FILE),故返回值为FILE*。


C语言默认在启动的时候会打开三个标准输入输出流文件:


stdin:键盘文件
stdout:显示器文件
stderr:显示器文件

C++默认在启动的时候会打开三个标准输入输出流文件:
cin:键盘文件
cout:显示器文件
cerr:显示器文件

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

int main()
{
        printf("Pid: %d\n", getpid());

        FILE *fp = fopen("log.txt", "w");
        if(fp == NULL){
                perror("fopen");
                return 1;
        }

        const char *message = "hello Linux message";
        //strlen(message)无需+1 
        fwrite(message, strlen(message), 1, stdout);

        fclose(fp);

        return 0;
}

三、过渡到系统,认识文件系统调用
文件其实是在磁盘上的,磁盘是外部设备,访问磁盘文件实际上是访问硬件!几乎所有的库只要是访问硬件设备,必定要封装系统调用接口!


1、open:返回文件描述符表下标

上图的大写选项均为宏,一个整数有32个比特位,一个比特位就能表示一种状态,所以在Linux中仅用一个整数就能同时传递多个比特位,向系统传递多个选项。接下来就让我们用代码阐述比特位级别的标志位传递方式:

#include <stdio.h>
//#include <unistd.h>
//#include <string.h>

#define ONE (1<<0)// 1
#define TWO (1<<1)// 2
#define THREE (1<<2)// 4
#define FOUR (1<<3)// 8

void show(int flags)
{
        if(flags&ONE) printf("hello function1\n");
        if(flags&TWO) printf("hello function2\n");
        if(flags&THREE) printf("hello function3\n");
        if(flags&FOUR) printf("hello function4\n");
}


int main()
{
        show(ONE);
        printf("-----------------\n");
        show(TWO);
        printf("-----------------\n");
        show(ONE|TWO);
        printf("-----------------\n");
        show(ONE|TWO|THREE);
        printf("-----------------\n");
        show(ONE|TWO|THREE|FOUR);
        printf("-----------------\n");
}

 接下来让我们实战演练一下吧:

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

int main()
{
        int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
        if(fd < 0)
        {
                printf("open file error\n");
                return 1;
        }
        return 0;
}

我们会发现一个奇怪的现象,我们打开文件时设置权限为0666,可为何log.txt的权限为0664呢?

这当然是因为当前文件的权限掩码为0022

那要是我们一定要创建文件权限为0666呢?main函数中umask清零即可。

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

int main()
{
        umask(0);//权限掩码清零
        int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
        if(fd < 0)
        {
                printf("open file error\n");
                return 1;
        }
        return 0;
}

2、close

3、write(覆盖式向文件写入,但不会清空)

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

int main()
{
        umask(0);
        //file descriptor: 文件描述符,fd,int
        int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
        if(fd < 0)
        {
                printf("open file error\n");
                return 1;
        }
        const char *message = "hello file system call";
        write(fd, message, strlen(message));//无需+1

        close(fd);
        return 0;
}

其余库函数的底层原理都是对系统调用接口的封装。

四、访问文件的本质
1、操作系统内描述一个被打开文件信息的结构体:struct file。
直接或者间接包含如下属性:在磁盘的位置,文件基本属性(权限,大小,读写位置,打开者),文件的内核缓冲区信息,struct file *next指针,引用计数count,文件描述符表,对应打开文件的缓冲区字段和维护信息。(该结构体对象属于用户,不属于操作系统)

2、而现在知道,文件描述符就是从 0 开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了FILE 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表 files_struct, 该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
3、
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
        umask(0);
        //file descriptor: 文件描述符,fd,int
        int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
        if(fd < 0)
        {
                printf("open file error\n");
                return 1;
        }
        printf("fd: %d\n", fd);
        const char *message = "hello file system call";
        write(fd, message, strlen(message));//无需+1

        close(fd);
        return 0;
}

可是文件描述符表的下标为什么是从三开始呢?

因为C语言默认在启动的时候,会打开三个标准输入流。
以下对应的文件描述符为:
stdio:键盘文件 0
stdout:显示器文件 1
stderr:显示器文件 2

4、所以这只是C语言的特性吗?不是,这是操作系统的特性进程,默认会打开键盘,显示器,显示器。

5、FILE是C库自己封装的结构体,这里面必须封装文件描述符。

6、文件描述符的分配规则:从0下标开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符。

总结:两层封装:①库函数封装系统调用接口 ②文件FILE*类型必定包含文件描述符

7、关闭文件的核心动作①引用计数--②文件描述符表数组下标置空。


五、输入输出重定向

1、重定向:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define filename "log.txt"

int main()
{
        //close();
        int fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }
        printf("fd: %d\n", fd);
        const char *msg = "hello Linux\n";
        int cnt = 5;
        while(cnt)
        {
                write(fd, msg, strlen(msg));
                cnt--;
        }
        close(fd);
        return 0;
}

以下是关闭不同fd的结果: 

        close(0);

        close(1);

        close(2);

 由此观之,文件描述符表的分配规则为:从0下标开始,寻找最小的没使用的数组位置,它的下标就是新文件的文件描述符。

②再来做个小实验:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define filename "log.txt"

int main()
{
        //close(1);
        int fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }
        //printf("fd: %d\n", fd);       
        const char *msg = "hello Linux\n";
        int cnt = 5;
        while(cnt)
        {
                write(1, msg, strlen(msg));
                cnt--;
        }
        close(fd);
        return 0;
}

 如上代码的结果是这样的:

[root@hecs-210801 lesson21]# ./mytest
hello Linux
hello Linux
hello Linux
hello Linux
hello Linux

关闭下标为1的fd:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define filename "log.txt"

int main()
{
        close(1);
        int fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);//该文件的fd变为1
        if(fd < 0)
        {
                perror("open");
                return 1;
        }
        //printf("fd: %d\n", fd);       
        const char *msg = "hello Linux\n";
        int cnt = 5;
        while(cnt)
        {
                write(1, msg, strlen(msg));
                cnt--;
        }
        close(fd);
        return 0;
}
[root@hecs-210801 lesson21]# ./mytest
[root@hecs-210801 lesson21]# cat log.txt
hello Linux
hello Linux
hello Linux
hello Linux
hello Linux

本该打印在显示器上的内容转而打印在了普通文件中,这就叫做输出重定向!

重定向的本质:是对进程的指定描述符表进行内核级别的文件对象地址作拷贝的工作。

2、重定向的接口:dup2

 ①输入重定向:

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

#define filename "log.txt"

int main()
{
        //close(1);
        int fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }
        //printf("fd: %d\n", fd);       
        //重定向
        dup2(fd, 1);
        close(fd);
        const char *msg = "hello Linux\n";
        int cnt = 5;
        while(cnt)
        {
                write(1, msg, strlen(msg));
                cnt--;
        }
        close(fd);
        return 0;
}

  ②输出重定向:

int main()
{
        int fd = open(filename, O_RDONLY);
        if(fd < 0)
        {
                perror("open");
                return 1;
        }

        dup2(fd, 0);

        char inbuffer[1024];

        ssize_t s = read(0, inbuffer, sizeof(inbuffer)-1);

        if(s>0)
        {
                inbuffer[s] = '\0';
                printf("echo %s\n", inbuffer);
        }

        close(fd);
        return 0;
}

3、 内存管理和文件操作解耦:进程历史打开的文件与进行的各种重定向关系都和未来进行程序替换无关!程序替换并不影响文件访问!

4、将stdout中内容写到stderr:

[root@hecs-210801 lesson21]# 2>&1

六、如何理解“一切皆文件”:
1、所有操作计算机的动作,都是以进程的形式进行操作的,所有访问文件的操作,最终都是由进程来访问文件的。

2、文件都可以以文件的形式被操作系统用open打开,每一个文件都要在内核中创建一个结构体对象(struct file),还创建了一个方法级对象(struct operation_func)(分为基类与派生类),通过struct file中的指针指向struct operation_func,其中的函数指针指向底层方法。

3、上层统一使用一个read接口,直接通过一个数据结构调用读方法写方法,它可以根据指针指向的不同,动态的根据不同的设备,使用不同的方法。(多态)

4、以上的设计称为VFS(虚拟文件系统)

七、重定向缓冲区

1、观察下列两组代码:

①C接口

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

int main()
{
        //C接口
        const char *fstr = "hello fwrite";
        printf("hello printf"); // stout ->1
        fprintf(stdout, "hello fprintf");// stout -> 1
        fwrite (fstr, strlen(fstr), 1, stdout);// fread, stout->1

        close(1);

        return 0;
}

输出结果:不打印任何内容 

[root@hecs-210801 lesson24]# make
gcc -o myfile myfile.c -std=c99
[root@hecs-210801 lesson24]# ./myfile

 ②系统调用接口

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

int main()
{
        //操作系统提供的systemcall
        const char *str = "hello write";
        write(1, str, strlen(str));

        close(1);

        return 0;
}

输出结果:打印

[root@hecs-210801 lesson24]# make
gcc -o myfile myfile.c -std=c99
[root@hecs-210801 lesson24]# ./myfile
hello write[root@hecs-210801 lesson24]# 

2、为什么会出现这种现象呢?
系统调用接口能直接通过进程找到数据对象将数据写到了系统内部缓冲区中,close自动将数据刷新打印。而C语言缓冲区是用户级缓冲区,不是系统级别的缓冲区,一定不在操作系统内部!只有在合适的时候,c库才会调用对应的write接口,将数据写入到操作系统里面。(语言都属于用户层,不属于操作系统。)

而调用close把1号文件描述符关闭,进程退出时无法刷新C语言缓冲区,所以C程序没有对应的显示结果。而系统接口通过操作系统直接写到系统级缓冲区内部,直接交给操纵系统的数据自然而然就能被刷新出来啦!(目前我们认为,只要将数据刷新到了内核,数据就可以到硬件了。)(一般情况下,在进程退出的时候也会刷新完成区。)

当然,使用\n能够立刻清空缓冲区,显示器的文件的刷新方案是行刷新,所以在printf执行遇到\n的时候,将数据进行刷新。(刷新的本质就是将数据通过1号文件描述符和write写入到内核中)。

 这不由得让我们想起了之前所讲的exit和_exit,感兴趣的同学请自行阅读上一篇文章——进程控制哦。

3、(应用层)缓冲区刷新策略:
①无缓冲--直接刷新。
②行缓冲--不刷新,直到碰到\n。
③全缓冲--缓冲区满了,才刷新(普通文件写入)(重定向:若将显示器打印变成向文件打印,缓冲方案变成了全缓冲,遇到\n不再刷新)。

4、为什么要有这个缓冲区?
①解决效率问题-用户的效率问题。
②配合格式化。

5、fork创建子进程时:
操作系统在对这个缓冲区做写入的时候,发生写时拷贝,父子进程各自私有一份,最后被系统向文件刷新了两份同样的数据。
①重定向刷新方案发生更改
②数据在写入时在缓冲区由刷新改为暂存,因为缓冲区没有被写满。
③fork后父子退出时均要刷新缓冲区,且共享该缓冲区,各自刷新一次。

八、模拟实现

Mystdio.h

//Mystdio.h

//#pragma once
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__

#include <string.h>

#define SIZE 1024

#define FLUSH_NOW 1 //立即刷新
#define FLUSH_LINE 2 //行刷新
#define FLUSH_ALL 4 //全刷新

typedef struct IO_FILE{
        int fileno;
        int flag;
        char inbuffer[SIZE];//输入缓冲区
        char outbuffer[SIZE];//输出缓冲区
        int in_pos;
        int out_pos;
}_FILE;


_FILE *_fopen(const char*filename, const char* flag);
int _fwrite(_FILE *fp, const char *s, int len);
void _fclose(_FILE *fp);

#endif

Mystdio.c 

//Mystdio.c

#include "Mystdio.h"

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

#define FILE_MODE 0666

//"w","a","r"
_FILE *_fopen(const char*filename, const char* flag)
{
        assert(filename);
        assert(flag);

        int f = 0;
        int fd = -1;
        if(strcmp(flag,"w") == 0)
        {
                f = (O_CREAT|O_WRONLY|O_TRUNC);
                fd = open(filename, f, FILE_MODE);
        }
        else if(strcmp(flag,"a") == 0)
        {
                f = (O_CREAT|O_WRONLY|O_APPEND);
                fd = open(filename, f, FILE_MODE);
        }
        else if(strcmp(flag,"w") == 0)
        {
                f = O_RDONLY;
                fd = open(filename, f);
        }
        else
        {
                return NULL;
        }
        if(fd == -1) return NULL;

        _FILE* fp =  (_FILE*)malloc(sizeof(_FILE));

        if(fp == NULL) return NULL;

        fp->fileno = fd;
        fp->flag = FLUSH_LINE;
        fp->out_pos = 0;

        return fp;

}

int _fwrite(_FILE *fp, const char *s, int len)
{
        memcpy(&fp->outbuffer[fp->out_pos], s, len);//没有做异常处理,也不考虑局部问题。
        fp->out_pos += len;

        if(fp->flag & FLUSH_NOW)
        {
                write(fp->fileno, fp->outbuffer, fp->out_pos);
                fp->out_pos = 0;
        }
        else if(fp->flag & FLUSH_LINE)
        {
                if(fp->outbuffer[fp->out_pos-1] == '\n')
                {
                        write(fp->fileno, fp->outbuffer, fp->out_pos);
                        fp->out_pos = 0;
                }
        }
        else if(fp->flag & FLUSH_ALL)
        {
                if(fp->out_pos == SIZE)
                {
                        write(fp->fileno, fp->outbuffer, fp->out_pos);
                        fp->out_pos = 0;
                }
        }

        return len;
}

void _fflush(_FILE *fp)
{
        if(fp->out_pos > 0)
        {
                write(fp->fileno, fp->outbuffer, fp->out_pos);
                fp->out_pos = 0;
        }
}

void _fclose(_FILE *fp)
{
        if(fp == NULL) return;
        _fflush(fp);
        close(fp->fileno);
        free(fp);
}                    

main.c

//main.c
#include "Mystdio.h"
#include <unistd.h>
#define myfile "test.txt"

int main()
{
        _FILE * fp = _fopen(myfile, "w");
        if(fp == NULL) return 1;
        const char *msg = "hello world\n";
        int cnt = 10;
        while(cnt)
        {
                _fwrite(fp, msg, strlen(msg));
                //fflush(fp);
                sleep(5);
                cnt--;
        }


        _fclose(fp);

        return 0;
}

 

 

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

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

相关文章

【docker】docker的基础命令

基础操作 docker info #查看docker的基本信息docker version #查看docker版本信息一、镜像操作 1、搜索镜像 docker search nginx2、下载镜像 docker pull nginx#从仓库中下载镜像&#xff0c;若没有指定标签&#xff0c;则下载最新的版本&#xff0c;也就是标签为: lat…

使用 STM32 读取和解析 NTC 热敏电阻的数值

本文介绍了如何利用 STM32 微控制器读取和解析 NTC&#xff08;Negative Temperature Coefficient&#xff09;热敏电阻的数值。首先&#xff0c;我们将简要介绍 NTC 热敏电阻的原理和特性。接下来&#xff0c;我们将详细讨论如何设计电路连接和采用合适的 STM32 外设进行数值读…

【超强笔记软件】Obsidian实现免费无限流量无套路云同步

【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步&#xff1f; 目录 一、简介 软件特色演示&#xff1a; 二、使用免费群晖虚拟机搭建群晖Synology Drive服务&#xff0c;实现局域网同步 1 安装并设置Synology Drive套件 2 局域网内同步文件测试 三、内网穿透群…

docker介绍、部署与常用命令

一、docker 介绍 1、容器&#xff08;Container&#xff09;&#xff1a; (1) 概念&#xff1a; 容器是一种用于运行和部署应用程序的技术。它将应用程序及其所有依赖项&#xff08;例如代码、运行时、系统工具、系统库等&#xff09;打包在一个独立的、可移植的运行环境中&…

Java多态:多态多态,多么变态

&#x1f451;专栏内容&#xff1a;Java⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、重写1、重写的规则2、重写与重载的区别 二、多态1、多态的概念2、多态的实现3、向上转移和向下转型Ⅰ、向上转型Ⅱ、向下转…

Linux加强篇004-Vim编辑器与Shell命令脚本

目录 前言 1. Vim文本编辑器 1.1 编写简单文档 1.2 配置主机名称 1.3 配置网卡信息 1.4 配置软件仓库 2. 编写Shell脚本 2.1 编写简单的脚本 2.2 接收用户的参数 2.3 判断用户的参数 3. 流程控制语句 3.1 if条件测试语句 3.2 for条件循环语句 3.3 while条件循环语…

spring aop核心原理概念

目录 概述aop核心概念解析Target(目标对象)Joinpoint(连接点)Advice(通知/增加)Pointcut(切入点)Aspect(切面)Advisor(通知器)Weaving(织入)Proxy(代理)Introduction(引介) 结束 概述 aop核心概念解析 Target(目标对象) 代理的目标对象 目标对象(Target)的确立&#xff0c;是…

C++ 实现位图

引出 面试题&#xff1a;给出 40 亿个不重复的无符号整数&#xff0c;没有排过序。给定一个无符号整数&#xff0c;如何快速判断这个数是否在这 40 亿个无符号整数中。[ 腾讯面试题 ] 想法一&#xff1a;将 40 亿个数据存放到 set 里面&#xff0c;然后再查找指定的无符号整数。…

深度学习技巧应用30-深度学习中的GPU的基本架构原理与应用技巧

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用30-深度学习中的GPU的基本架构原理与应用技巧,GPU是一种专门用于处理大量并行操作的硬件设备,它的架构设计主要是为了图形渲染。然而,由于其并行处理能力,现在广泛应用于深度学习、科学计算等领域。主要的GPU制造商…

运维高级--centos7源码安装Apache

安装必要的依赖项&#xff1a; sudo yum groupinstall "Development Tools" sudo yum install pcre pcre-devel zlib zlib-devel openssl openssl-devel这将安装编译和构建所需的基本工具&#xff0c;以及 Apache HTTP Server 所需的一些依赖项。 下载 Apache HTT…

从0到0.01入门 Webpack| 008.精选 Webpack面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【网络】DNS协议、ICMP协议、NAT技术

DNS协议、ICMP协议、NAT技术 一、DNS协议1、产生背景2、域名简介3、域名解析的工作流程4、使用dig工具分析DNS过程 二、ICMP协议1、ICMP介绍2、ICMP协议格式3、ping命令4、traceroute命令 三、NAT技术1、NAT技术背景2、NAT IP转换过程3、地址转换表4、NAPT技术5、重新理解路由器…

JsonRPC协议详解(协议介绍、请求示例、响应示例)

JsonRPC协议详解 什么是RPC&#xff1f; RPC&#xff08;远程过程调用&#xff09;是一种用于实现分布式系统中不同进程或不同计算机之间通信的技术。它允许我们像调用本地函数一样调用远程计算机上的函数&#xff0c;使得分布式系统的开发变得更加简单和高效。 什么是JsonRP…

【数据结构实验】图(三)图的深度优先搜索(DFS)生成树

文章目录 1. 引言2. 深度优先搜索生成树3. 实验内容3.1 实验题目&#xff08;一&#xff09;输入要求&#xff08;二&#xff09;输出要求 3.2 算法实现1. 数据结构2. 队列操作函数3. 广度优先搜索遍历4. 创建图5. 深度优先搜索算法6. 主函数及DFS主函数7. 输出生成树信息 3.3 …

第一百八十回 介绍两种阴影效果

文章目录 1. 概念介绍2. 实现方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"自定义SlideImageSwitch组件"相关的内容&#xff0c;本章回中将介绍两种阴影效果.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在…

【服务器能干什么】二十分钟搭建一个属于自己的 RSS 服务

如果大家不想自己捣鼓,只是想尝尝鲜,可以在下面留言,我后台帮大家开几个账号玩一玩。 哔哩哔哩【高清版本可以点击去吐槽到 B 站观看】:【VPS服务器到底能干啥】信息爆炸的年代,如何甄别出优质的内容?你可能需要自建一个RSS服务!_哔哩哔哩_bilibili 前言 RSS 服务 市…

面试:双线程交替打印奇偶数

代码如下&#xff1a; package practice1;/*** 0-100的奇数偶数打印* 1、通过对象的wait和notify进行线程阻塞* 2、通过对num%2的结果进行奇数偶数的判断输出**/ public class JiOuOne {private static volatile int num 0;private static final int max 100;public static …

#define例题

我们已经学了#define的所有知识&#xff0c;让我们来看这道题&#xff0c;可不要又陷入陷阱 题目要求&#xff1a; #define N 4 #define Y(n) ((N2)*n) int main() {int z 2 * (N Y(5 1));printf("z%d\n", z);return 0; } 求这个z的值是多少&#xff1f; 我们直接…

Stable-Diffusion——Windows部署教程

Windows 参考文章&#xff1a;从零开始&#xff0c;手把手教你本地部署Stable Diffusion Webui AI绘画(非最新版) 一键脚本安装 默认环境安装在项目路径的venv下 conda create -n df_env python3.10安装pytorch&#xff1a;&#xff08;正常用国内网就行&#xff09; python -…

Sublime Text 3 安装离线插件 anaconda

1 下载 Sublime Text 3 免安装版 Download - Sublime Text 2 下载 Package Control&#xff0c;放到 Sublime Text Build 3211\Data\Installed Packages 目录下。 Installation - Package Control 3 页面搜索 anaconda anaconda - Search - Package Control Anaconda - Pac…