Linux应用开发之文件与IO流

news/2024/5/4 10:38:25/文章来源:https://blog.csdn.net/geek_liyang/article/details/129694897
与大多数操作系统一样,Linux为程序运行提供了大量的服务,包括打开文件、读文件、启动一个新程序、分配存储区以及获得当前时间等,这些服务被称为系统调用接口(system call interface)。另外,glibc库还提供了大量广泛用于C程序的通用函数(格式化输出变量的值,比较两个字符串等)。
基于Linux系统的程序设计接口(系统调用接口和C库提供的很多函数),实现业务控制逻辑,我们谓之为Linux应用开发。本篇文章主要针对Linux应用开发中遇到的与文件和I/O相关的接口和知识点,进行讲解,帮助读者初体会一切皆文件的Linux基本哲学。讲解延续一贯风格,会先抛出问题或者是例子供读者思考,再进行分析。 💹

首先,说明一下几个标准的含义,方便读者后续的归类理解。

  1. ISO C:意图是提供C程序的可移植性,使得它能够适合于大量不同的操作系统。该标准不仅定义了C程序设计语言的语法和语义,还定义了标准库

  1. POSIX(Portable Operating System Interface): 指的是可移植操作系统接口。该标准的目的是提升应用程序在各种UNIX系统环境之间的可移植性。它定义了“符合POSIX”的操作系统必须提供的各种服务。

  1. SUS(Single Unix Specification):是 POSIX 标准的一个超集,他定义了一些附加接口扩展了 POSIX 规范提供的功能。

上述三个标准只是定义了接口的规范,而具体的实现由厂商来完成,Linux就是这种标准的一个实现,目前同属于UNIX标准的有一下几个操作系统:

  • SVR4(UNIX System V Release 4)

  • 4.4 BSD(Berkeley Software Distribution)

  • FreeBSD

  • Linux

  • Mac OS X

  • Solaris


系统基础知识

UNIX系统

UNIX内核的接口称之为系统调用。公用函数库构建在系统调用接口之上。应用程序既可以使用公用函数库,也可以使用系统调用。UNIX shell 是一个特殊的应用程序,它为其他应用程序提供了一个接口。每个进程都有一个工作目录,有时称他为当前工作目录。所有的相对路径名都是从工作目录开始解释。文件系统根的名字"/"是一个特殊的绝对路径名。它不包含任何其他的字符。

Linux系统提供了强大的帮助说明文档,比如查看UNIX系统ls命令的 man帮助手册:man 1 ls或者man -s1 lsls表示待查找的shell command。1表示第一部分。由于很多命令的说明文档过于庞大,因此 UNIX将说明文档划分成九个部分。常用的段有:

  1. 可执行程序或者shell command的说明

  1. 系统调用的说明

  1. 库函数的说明

  1. 特殊文件(通常位于/dev/)的说明

  1. 系统管理员命令的说明(通常只有root用户可用)

我们可以将 LINUX 操作系统中的 man手册用中文的man手册替代。方法为(Ubuntu操作系统下):

sudo apt-get install manpages-zh
sudo vi /etc/manpath.config

最后将 man的配置文件/etc/manpath.config中所有的/usr/share/man替换为/usr/share/man/zh_CN即可


错误管理

当 UNIX 系统函数出错时,通常会返回一个负值,同时整型变量 erron 通常被设置为具有特定信息的值。

#include<string.h>
char *strerror(int errnum);
#include<stdio.h>
void perror(const char*msg);

上边两个函数用于处理错误信息。常用的是perror(),下面是函数使用代码和结果截图:

#include<string.h>
#include<stdio.h>
#include<errno.h>
void test_perror(int new_errno,const char*msg)
{M_TRACE("---------  Begin test_perror(%d,\"%s\")  ---------\n",new_errno,msg);int old_errno=errno;  //保存旧值errno=new_errno;  // 赋新值printf("perror(\"%s\") of errno=%d\n",msg,errno);perror(msg);errno=old_errno; // 还原旧值M_TRACE("---------  End test_perror(%d,\"%s\")  ---------\n\n",new_errno,msg);
}

系统限制

UNIX 系统定义两种类型的限制,一种是编译时限制,如short、int 最大值是多少,还有一种是运行时限制,如文件名最长多少个字符。常编译时限制可以在头文件中定义;运行时限制则要求进程调用一个函数获得限制值。POSIX 与ISO C限制定义了很多如整型大小以及涉及操作系统实现限制的常量。这些常量大多数在<limits.h>中。运行时限制可通过下面三个函数给定。

#include<unistd.h>
long sysconf(int name); 
long pathconf(const char*pathname,int name);
long fpathconf(int fd,int name); //fd 为文件描述符

文件与目录

文件描述符 :一个非负整数,范围是0~OPEN_MAX-1。内核用它来标识进程正在访问的文件。当进程创建时,默认为它打开了3个文件描述符,它们都链接向终端:

  • 0: 标准输入

  • 1: 标准输出

  • 2: 标准错误输出

通常我们应该使用STDIN_FILENO,STDOUT_FILENO和 STDERR_FILENO来替代这三个幻数(幻数:就是具体的数,反映不出数字所代表的意义。),从而提高可读性。这三个常量位于<unistd.h>中。

文件IO操作

#include<fcntl.h>
#include<unistd.h>
int open(const char* path,int oflag,.../*mode_t mode*/);
int creat(const char*path,mode_t mode);
int close(int fd);
off_t lseek(int fd, off_t offset,int whence);
ssize_t read(int fd,void *buf,size_t nbytes);
ssize_t write(int fd,const void *buf,size_t nbytes);

上述六个函数是我们常用的文件IO操作,具体我就不一一说明了,我们使用上面函数来创建一个有趣的文件。下面的代码会创建一个size很大,但是占用空间很小的文件:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>int main(int argc, char* argv[]) {int fd;if(argc < 2) {fprintf(stderr,"Usage %s <filename>\n", argv[0]);exit(1);}fd = open(argv[1],O_WRONLY | O_CREAT | O_TRUNC, 0777);if(fd < 0) {perror("fopen()");}lseek(fd, 5ll*1024ll*1024ll*1024ll-1ll, SEEK_SET);write(fd," ",1);close(fd);exit(0);
}

原子操作与描述符管理

内核使用三种数据结构描述打开文件。它们之间的关系决定了一个进程与另一个进程在打开的文件之间的相互影响。现在假设进程 A 打开文件 file1,返回文件描述符 3;进程 B 也打开文件 file2,返回文件描述符 2:

进程 B要往一个文件追加写数据,使用 lseek 定位到文件当前的尾端,则进程 B 对应的文件表项的当前文件偏移量设置为 i 结点中的当前长度。若此时进程B被打断,进程A通过 O_APPEND选项打开文件,然后直接write,若CPU控制权再回到进程B时,此时再继续往刚才lseek到的位置写,就会出现问题。使用下面两个原子定位读和原子定位写接口就能将lseek和read/write绑定为原子操作,则不会出现上述的问题。

#include<unistd.h>
ssize_t pread(int fd,void*buf,size_t nbytes,off_t offset);
ssize_t pwrite(int fd,const void*buf,size_t nbytes,off_t offset);

复制一个现有的文件描述符有dup()和dup2()两个接口,dup2()用把新文件描述符关闭再打开做成了原子操作,防止close()之后有别的线程捕获文件关闭信号之后修改文件描述符而导致一些同步的问题。下面是dup2使用的一个例子:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include <unistd.h>
#define FNAME "/tmp/out"int main() {int fd;fd = open(FNAME, O_WRONLY | O_CREAT | O_TRUNC, 0777);if(fd < 0) {perror("open()");exit(1);}// 如果没有使用文件描述符号就会出错,不原子// close(1);// dup(fd);dup2(fd,1);if(fd != 1) {clse(1);}puts("hello");exit(0);
}

UNIX操作系统在内核中设有缓冲区,大多数磁盘 I/O 都通过缓冲区进行。当我们想文件写入数据时,内核通常都首先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式称为延迟写。有很多手动将延迟写的数据库写入磁盘的接口如sync()。下面这个向文件里输出时间的例子使用了标准IO库里的fflush()函数来冲洗了一个流。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>#define FILE_NAME "/tmp/out"
#define BUF_SIZE 1024int main(int argc, char* argv[]) {FILE* fp;fp = fopen(FILE_NAME, "a+");if (NULL == fp) {perror("fopen()");exit(1);}char buf[BUF_SIZE];int count = 0;while (NULL != fgets(buf, BUF_SIZE, fp)) {++count;}time_t stamp;struct tm* tm;while (1) {time(&stamp);tm = localtime(&stamp);fprintf(fp, "%-4d%d-%d-%d %d:%d:%d\n", count++, tm->tm_year + 1900,tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);//文件流采用全缓冲模式,手动刷新一下比较好。fflush(fp);sleep(1);}fclose(fp);exit(0);
}

fcntl函数用于改变已经打开的文件的属性,项目中也会用到,具体的使用说明可以使用man手册看一下。/dev/fd目录下是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符(假定描述符n是打开的)


文件与目录

文件与目录这部分的接口繁杂,UNIX文件系统、硬链接、软链接、及其删除与重命名操作;stat 查询文件结构和权限相关;创建不具有不同访问权限的文件,修改文件访问权限和文件所属用户,文件长度以及文件的时间属性等;还有很多诸如opendir()的目录操作接口。

这些不同类型的接口往往都有shell命令帮助我们在终端对文件和目录进行操作,下面的例子是手写了一个du命令的实现,通过这个例子希望读者能够理解Linux环境的文件与目录操作。

#include <dirent.h>
#include <glob.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define PATHSIZE 4096// 判断目录是否指向自己或上级目录
static int path_nolopp(const char *path) {  char *pos = strrchr(path, '/');if (!pos) {exit(1);}if (0 == strcmp(pos + 1, ".") || 0 == strcmp(pos + 1, "..")) {return 0;} else {return 1;}
}// 主函数返回目录或文件的大小单位为KB
static long long mydu(char *path) {int sum = 0;static struct stat statres;if (lstat(path, &statres) < 0) {perror("lstat()");exit(1);}//非目录文件if (!S_ISDIR(statres.st_mode)) {return statres.st_blocks / 2;}//目录文件glob_t globres;  // glob_t是typedef 定义的别名static char nextPath[PATHSIZE];sum = statres.st_blocks / 2;  //加上目录文件本身strncpy(nextPath, path, PATHSIZE);strncat(nextPath, "/*", PATHSIZE-1);glob(nextPath, 0, NULL, &globres);strncpy(nextPath, path, PATHSIZE);strncat(nextPath, "/.*", PATHSIZE-1);glob(nextPath, GLOB_APPEND, NULL, &globres);for (int i = 0; i < globres.gl_pathc; ++i) {if (path_nolopp(globres.gl_pathv[i])) {sum += mydu(globres.gl_pathv[i]);}}globfree(&globres);return sum;
}int main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usege %s <dir_name>\n", argv[0]);exit(1);}mydu(argv[1]);printf("%d\n", mydu(argv[1]));exit(0);
}

标准&高级I/O库

标准IO库的函数很多都是以 f开头,如fopen、fclose。标准IO库与文件IO区别如下:

  • 标准IO库处理很多细节,如缓冲区分片、以优化的块长度执行IO等。

  • 文件IO函数都是围绕文件描述符进行。首先打开一个文件,返回一个文件描述符;后续的文件IO操作都使用该文件描述符。

  • 标准IO库是围绕流进行的。当用标准IO库打开或者创建一个文件时,就有一个内建的流与之相关联。

标准I/O库

当使用fopen函数打开一个流时,它返回一个执行FILE对象的指针。该对象通常是一个结构,包含了标准IO库为管理该流所需要的所有信息,包括:

  • 用于实际IO的文件描述符

  • 指向用于该流缓冲区的指针

  • 该流缓冲区的长度

  • 当前在缓冲区中的字符数

  • 出错标志

应用程序没必要检验FILE对象,只需要将FILE指针作为参数传递给每个标准IO函数。

对于ASCII字符集,一个字符用一个字节表示;对于国际字符集,一个字符可以用多个字节表示。这就导致了流的宽度可能不一样。fwide函数能够修改流的宽度,是宽(多字节)定向还是字节定向。

操作系统对每个进程与定义了3个流,并且这3个流可以自动地被进程使用,他们都是定义在<stdio.h>中:

  • 标准输入:预定义的文件指针为stdin,它内部的文件描述符就是STDIN_FILENO

  • 标准输出:预定义的文件指针为stdout,它内部的文件描述符就是STDOUT_FILENO

  • 标准错误:预定义的文件指针为stderr,它内部的文件描述符就是STDERR_FILENO

标准IO库提供缓冲的目的是:尽量减少使用readwrite调用的次数。标准IO库对每个IO流自动地进行缓冲管理,从而避免了程序员需要手动管理这一点带来的麻烦。

标准IO库提供了三种类型的缓冲:

  1. 全缓冲:此时在标准IO缓冲区被填满后,标准IO库才进行实际的IO操作。

  1. 行缓冲:此时当输入和输出中遇到换行符时,标准IO库执行实际的IO操作。

  1. 不带缓冲:标准IO库不对字符进行缓冲存储。此时任何IO都立即执行实际的IO操作。

可以通过setbuf()/setvbuf()函数来设置流的缓冲类型。缓冲这块尤其针对行缓冲以及标准流有一些很细的问题,这里就不详述了,我们常用的还是上面文件IO操作中的向文件里输出时间那个例子一样,使用fflush函数来将缓冲中的数据冲洗到磁盘上。

标准1/O库提供了很多操作文件流的接口,包括打开关闭流;读写流;格式化输入输出;以及像fmemopen()等其他接口。我们还是通过一个实际的例子,来体会这些接口的使用。

下面这个mycopy.c使用getc()与putc()接口实现了一个复制文件的功能:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char* argv[]) {FILE *fps, *fpd;// 出错时返回负值不要用char。int ch;if(argc < 3) {fprintf(stderr,"Usage:%s <src_file> <dst_file>\n",argv[0]);exit(1);}fps = fopen(argv[1], "r");if (NULL == fps) {perror("fopen(fps)");exit(1);}fpd = fopen(argv[2], "w");if (NULL == fpd) {// 不过关闭会导致内存泄漏。fclose(fps);perror("fopen(fpd)");exit(1);}while (1) {ch = fgetc(fps);if (EOF == ch) break;fputc(ch, fpd);}puts("well down!");fclose(fpd);fclose(fps);exit(0);
}

我们编写下面的脚本来编译这个文件,并使用它实现对first_paper.zip压缩包的拷贝,并在控制台打印程序所消耗的时钟时间,用户CPU时间以及系统CPU时间:

#!/bin/bash
BUFSIZE=128
let MAXBUF=2**$1
let i=1
while [ $BUFSIZE -le  $MAXBUF ] 
doechoecho "$i" let i+=1echo "$BUFSIZE"gcc mycopy.c -DBUFSIZE=$BUFSIZEtime ./a.out ~/Documents/first_paper.zip ~/Downloadrm -f ~/Downloads/first_paper.zip let BUFSIZE*=2
done

高级IO

Linux系统提供了很多高级IO接口来帮助我们实现对“文件"(这里的文件可能代表着一个诸如串口的设备)的不同操作需求。我们可以使用fcntl将文件设置为非阻塞访问,可以避免盲等设备的回复。我们也可以使用记录锁阻止其他进程修改同一个文件区。

我们嵌入式开发常见的需求是IO多路转换,即能监视多个fd,一旦某fd就绪(读或写就绪),能够通知程序进行相应读写操作。涉及的接口有select()、poll()以及epoll()。

我在实际的项目中用的就是epoll来监控的多路串口,因为select,poll需自己主动不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但它是设备就绪时,调用回调函数,把就绪fd放入就绪链表,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但select和poll在“醒着”时要遍历整个fd集合,而epoll在“醒着”的时候只需判断就绪链表是否为空,节省大量CPU时间,这就是回调机制带来的性能提升。

下面是一个使用epoll()接口来完成我们嵌入式应用开发常见的"中继"需求,即从一个串口接收到的数据,转发到另一个串口。实际的项目应用过程中还可能还涉及到协议的拆包和封包等工作,下面的例子省去了这一部分,只实现了"透传"的功能。

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFFSIZE 1024enum { STATE_R = 1, STATE_W, STATE_AUTO, STATE_Ex, STATE_T };struct fsm_st {int state;int sfd;int dfd;char buf[BUFFSIZE];int len;int pos;char* err;
};static int max(int a, int b) { return a > b ? a : b; }static void fsm_driver(struct fsm_st* fsm) {switch (fsm->state) {case STATE_R: {fsm->len = read(fsm->sfd, fsm->buf, BUFFSIZE);if (0 == fsm->len) {fsm->state = STATE_T;} else if (fsm->len < 0) {if (errno == EAGAIN) {fsm->state = STATE_R;} else {fsm->err = "read()";fsm->state = STATE_Ex;}} else {fsm->pos = 0;fsm->state = STATE_W;}break;}case STATE_W: {int ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);if (ret < 0) {if (errno == EAGAIN) {fsm->state = STATE_W;} else {fsm->err = "write()";fsm->state = STATE_Ex;}} else {fsm->pos += ret;fsm->len -= ret;if (0 == fsm->len) {fsm->state = STATE_R;} else {fsm->state = STATE_W;}}break;}case STATE_Ex: {fprintf(stderr, fsm->err);fsm->state = STATE_T;break;}case STATE_T: {// do something!break;}default: {abort();break;}}
}static void relay(int fd1, int fd2) {struct fsm_st fsm12, fsm21;struct epoll_event ev;//全部转化为非阻塞状态int fd1_save = fcntl(fd1, F_GETFL);fcntl(fd1, F_SETFL, fd1_save | O_NONBLOCK);int fd2_save = fcntl(fd2, F_GETFL);fcntl(fd2, F_SETFL, fd2_save | O_NONBLOCK);fsm12.state = STATE_R;fsm12.sfd = fd1;fsm12.dfd = fd2;fsm21.state = STATE_R;fsm21.sfd = fd2;fsm21.dfd = fd1;int epfd = epoll_create(10);if (epfd < 0) {perror("epoll_create()");exit(1);}ev.events = 0;ev.data.fd = fd1;epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev);ev.events = 0;ev.data.fd = fd2;epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev);while (fsm12.state != STATE_T || fsm21.state != STATE_T) {// 布置监视任务ev.data.fd = fd1;ev.events = 0;if (STATE_R == fsm12.state) {ev.events |= EPOLLIN;}if (STATE_W == fsm21.state) {ev.events |= EPOLLOUT;}epoll_ctl(epfd, EPOLL_CTL_MOD, fd1, &ev);ev.data.fd = fd2;ev.events = 0;if (STATE_R == fsm21.state) {ev.events |= EPOLLIN;}if (STATE_W == fsm12.state) {ev.events |= EPOLLOUT;}epoll_ctl(epfd, EPOLL_CTL_MOD, fd2, &ev);// 监视if (fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO) {while (epoll_wait(epfd, &ev, 1, -1) < 0) {if (EINTR == errno) {continue;}perror("epoll()");exit(1);}}// 查看监视结果if (ev.data.fd == fd1 && ev.events & EPOLLIN ||ev.data.fd == fd2 && ev.events & EPOLLOUT || fsm12.state > STATE_AUTO) {fsm_driver(&fsm12);}if (ev.data.fd == fd2 && ev.events & EPOLLIN ||ev.data.fd == fd1 && ev.events & EPOLLOUT || fsm21.state > STATE_AUTO) {fsm_driver(&fsm21);}}fcntl(fd1, F_SETFL, fd1_save);fcntl(fd2, F_SETFL, fd2_save);close(epfd);
}int main() {int fd1, fd2;fd1 = open(TTY1, O_RDWR);if (fd1 < 0) {perror("open()");exit(1);}write(fd1, "TTY1\n", 5);fd2 = open(TTY2, O_RDWR | O_NONBLOCK);if (fd1 < 0) {perror("open()");exit(1);}write(fd2, "TTY2\n", 5);relay(fd1, fd2);close(fd1);close(fd2);exit(0);
}

上述的接口本质上还是同步I/O,POSIX异步IO接口为不同类型的文件进行异步IO提供了一套一致的方法。Linux还提供了readv()与writev()用于在一次函数调用中读、写多个非连续缓冲区。我们嵌入式常用的存储映射IO接口mmap(),下面这个例子就是用这个接口,实现了父子进程之间的通信。父子进程在本篇博文有点超纲,下一篇您可以看到相关的介绍,目前您可以理解成两个独立的任务。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#define MEMSIZE 1024
int main() {char *ptr = mmap(NULL, MEMSIZE, PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS, -1, 0);if (MAP_FAILED == ptr) {perror("mmap()");exit(1);}pid_t pid = fork();if (pid < 0) {perror("fork");munmap(ptr, MEMSIZE);exit(1);}if (0 == pid) {strcpy(ptr, "hello!");munmap(ptr, MEMSIZE);exit(0);} else {wait(NULL);puts(ptr);munmap(ptr, MEMSIZE);exit(0);}
}

十六宿舍 原创作品,转载必须标注原文链接。
©2023 Yang Li. All rights reserved.
欢迎关注 『十六宿舍』 ,大家喜欢的话,给个 👍 ,更多关于嵌入式相关技术的内容持续更新中。

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

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

相关文章

移除链表元素

给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 示例 2&#xff1a; 输入&#xff…

使用IDEA把项目上传到gitee仓库

使用IDEA把项目上传到gitee仓库在gitee上建立一个仓库第一步&#xff08;新建仓库&#xff09;第二步&#xff08;点击创建&#xff09;第三步&#xff08;复制仓库地址&#xff09;创建工程第一步&#xff08;选择工程所在文件夹&#xff09;第二步&#xff08;文件加入git&am…

qq怎么安装不了(QQ怎么都安装不上重装也不行,是哪里出了问题?)

qq怎么安装不了(QQ怎么都安装不上重装也不行&#xff0c;是哪里出了问题&#xff1f;) 一、发现问题 今天有朋友说他电脑怎么都装不上QQ&#xff0c;总是弹出“安装包可能被非法改动导致安装失败&#xff0c;请从官网下载最新安装包重新安装”&#xff0c;操作系统是XP&#…

[数据结构高频面试题]用两个栈实现队列详解

文章目录 一、栈实现队列的特点分析 1、1 具体分析 1、2 整体概括 二、用栈模拟队列代码的实现 2、1 手撕 栈 代码 2、1、1 stack.h 2、1、2 stack.c 2、2 用栈实现队列代码 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x1f64b;‍♂️ &#x1f440; 专栏&#xff1a;…

Flink- 物理分区、Sink输出

物理分区 随机分区&#xff08;shuffle&#xff09; 轮询分区&#xff08;Round-Robin&#xff09; 重缩放分区&#xff08;rescale&#xff09; 广播&#xff08;broadcast&#xff09; 全局分区&#xff08;global&#xff09; 自定义分区&#xff08;Custom&#xff09; …

Studio One6中文语言版DAW数字音频音乐创作软件

Studio One6是一款非常实用的数字音乐创作软件&#xff0c;专门用于创作现代化音乐&#xff0c;软件具有简洁的界面和强大的功能&#xff0c;能够很好地辅助用户创作音乐。顾名思义就是“一个工作室”的意思&#xff0c;它所倡导的制作理念是直接在一个制作软件里完成音乐制作的…

Android 解包payload.bin文件,获取system.img

解析payload.bin获取.img文件 payload.bin payload.bin是Android OTA镜像打包文件&#xff0c;将包括system.img、boot.img和lk.img等在内的Android系统进行&#xff0c;打包为一个payload.bin文件。 在系统OTA过程中&#xff0c;系统会自动解压安装。 前期准备 需要安装py…

学习Java日志框架之——搞懂日志门面(JCL+SLF4J)

文章目录一、什么是日志门面1、门面模式&#xff08;外观模式&#xff09;2、日志门面二、了解JCL1、JCL组件结构2、JCL案例&#xff08;1&#xff09;JCL默认实现&#xff08;2&#xff09;导入log4j测试原有程序三、SLF4J简介四、SLF4J基本使用1、入门案例2、动态打印信息3、…

一次内存泄露排查

前因&#xff1a; 因为测试 长时间压测导致 接口反应越来越慢&#xff0c;甚至 导致服务器 崩溃 排查过程 1、top 查看是 哪个进程 占用 内存过高 2、根据 进程 id 去查找 具体是哪个 程序的问题 ps -ef| grep 41356 可以看到 具体的 容器位置 排查该进程 对象存活 状态…

23年PMP考试会使用第七版教材吗?

大家都知道了&#xff0c;今年的考纲是改版了的&#xff0c;为啥要改版呢&#xff0c;因为《PMBOK指南》更新到第七版了&#xff0c;考纲自然也要更新&#xff0c;据PMI的市场调查&#xff0c;近年来&#xff0c;项目管理行业新趋势在第六版和旧考纲中未收纳&#xff0c;为了确…

三、数据链路层

&#xff08;一&#xff09;纠错与检错1、奇偶校验码&#xff08;再研究下&#xff0c;原理知道&#xff0c;具体过程无法重现&#xff09;分为奇校验和偶校验&#xff0c;奇偶校验位在首部或尾部&#xff0c;奇偶校验满信息位奇偶校验位&#xff08;1&#xff09;原理&#xf…

Redis 数据结构

这里写目录标题Redis 数据结构一、String类型String数据类型的使用场景key 的设置约定二、Hash数据类型string存储对象&#xff08;json&#xff09;与hash存储对象的区别三、list 类型四、set 类型set数据交并差操作set 类型数据操作的注意事项六、sorted_set 类型Redis 数据结…

算法----火柴拼正方形

题目 你将得到一个整数数组 matchsticks &#xff0c;其中 matchsticks[i] 是第 i 个火柴棒的长度。你要用 所有的火柴棍 拼成一个正方形。你 不能折断 任何一根火柴棒&#xff0c;但你可以把它们连在一起&#xff0c;而且每根火柴棒必须 使用一次 。 如果你能使这个正方形&a…

Junit单元测试框架

1)Junit是一个开源的JAVA语言的单元测试框架&#xff0c;也是JAVA方向使用最广泛的单元测试框架&#xff0c;使用JAVA开发者都应该学习junit框架&#xff0c;并且掌握单元测试的编写 2)selenium和Junit都可以被导入到maven项目里面 3)先进行创建maven项目&#xff0c;导入相关依…

linux 全局环境变量删除后 还有 仍然存在

linux 全局环境变量删除后 还有 仍然存在1、编辑 /etc/profile2、设置REDISCLI_AUTH后&#xff0c;redis-cli 进去redis后不需要再次认证2、删除全局环境后 source后 仍然存在3、unset释放全局环境变量4、总结1、编辑 /etc/profile 设置redis环境变量 在末尾加入一行 export R…

家电企业数字工厂系统解决方案

国内小型家电生产商的中小企业普遍使用传统的手工作业模式&#xff0c;依靠大量的人力&#xff0c;线下管理各种数据&#xff0c;如&#xff1a;纸质文档、excel制作等&#xff0c;信息化程度非常低&#xff0c;严重限制着企业生产效率的提升和生产规模的扩大。对传统制造企业来…

基于WEB的网上购物系统的设计与实现(附:源码 论文 sql文件)

摘 要 随着计算机网络技术的飞速发展和人们生活节奏的不断加快&#xff0c;电子商务技术已经逐渐融入了人们的日常生活当中&#xff0c;网上商城作为电子商务最普遍的一种形式&#xff0c;已被大众逐渐接受。因此开发一个网上商城系统&#xff0c;适合当今形势&#xff0c;更加…

AWS白皮书 – 成本优化

本文讲解AWS良好架构框架&#xff08;AWS Well-Architected Framework&#xff09;里其中五大支柱之一&#xff1a;成本优化&#xff08;Cost Optimization&#xff09;。 一套成本优化型系统应充分利用全部资源、以最低价格来实现业务成果&#xff0c;同时充分满足你的功能需…

Google Bard VS ChatGPT:哪个是更好的AI聊天机器人?

文章目录前言一、Bard和ChatGPT的宏观对比二、应用场景不同三、知识的时效性四、未来的归宿总结前言 自从 OpenAI 向公众发布ChatGPT以来的过去几个月里&#xff0c;我们都见证了围绕 ChatGPT 的各种测评&#xff0c;并为它带来的效果感到惊艳。 昨晚Google开放了自家研发的A…

SpringBoot的简介和使用

文章目录1. SpringBoot简介和概述2. SpringBoot的使用3.SpringBoot 项目打包及运行4.切换web服务器1. SpringBoot简介和概述 Spring Boot是由Pivotal团队提供的一套开源框架&#xff0c;可以简化spring应用的创建及部署。它提供了丰富的Spring模块化支持&#xff0c;可以帮助开…