Linux——基础IO

news2024/10/7 6:10:45

目录

C语言文件操作        

fprintf​编辑

Linux下的文件操作(文件的系统调用接口) 

open

open的第三个参数

open的第二个参数

write

 read

文件描述符fd

进程与被打开文件的关系(理解的关键)

见见猪跑

fd文件描述符的分配规则

结论

重定向

输入重定向原理

 输出重定向原理

 dup2

dup2函数使用 

将重定向功能添加到myshell中

缓冲区的理解

举例引出缓冲区概念

生活相关

计算机相关 

 现象解释

缓冲区的刷新策略

模拟实现C语言中的fopen、fwrite、fclose、fflush

Makefile

myStdio.c

myStdio.h

main.c


C语言文件操作        


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


#define FILE_NAME  "log.txt"


// C语言操作
  int main()
  {
  FILE *fp = fopen(FILE_NAME, "w"); // r,w r+(读写,不存在出错),w+(读写,不存在创建)
  // FILE *fp=fopen(FILE_NAME,"r");//r,w r+(读写,不存在出错),w+(读写,不存在创建)
  // FILE *fp=fopen(FILE_NAME,"a");//r,w r+(读写,不存在出错),w+(读写,不存在创建)
  //  a+ 追加
  if (NULL == fp)
  {
    perror("fopen"); //
    return 1;
  }

  // // char buffer[64];

  // // //fgets是会给我们的字符串自动添加一个\0的 因此我们少传一个以至于让fegts有位置放\0
  // // while (fgets(buffer, sizeof(buffer) - 1, fp) != NULL)
  // // {
  // //   buffer[strlen(buffer)-1] = 0;//去掉\n
  // //   puts(buffer);
  // // }

  // //fprintf:将指定(信息)字符串输出到指定文件中
  // int cnt=5;
  // while(cnt)
  // {
  //   fprintf(fp,"%s:%d\n","hello mwb",cnt--);
  // }

  // 文件关闭
  fclose(fp);

  return 0;
}

fprintf

Linux下的文件操作(文件的系统调用接口) 

open

 当我们打开一个不存在的文件时,如果没有对其设置具体权限,所产生的文件权限将会混乱。

open的第三个参数

 当我们手动设置好权限后,就不会出现上图出现的情况了。

还可以手动消除系统中默认的文件掩码

umask(0);

open的第二个参数

使用位的或运算,对应需要以什么方式打开文件。

 根据所需进行或运算。

write


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

#define FILE_NAME  "log.txt"


int main()
{

  
  int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);// 追加
  // int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);//清空原文件内容
  // int fd = open(FILE_NAME, O_RDONLY);
  if (fd < 0)
  {
    perror("open");
    return 1;
  }

  

  int cnt = 5;
  char outBuffer[64];
  while (cnt)
  {
    sprintf(outBuffer, "%s:%d\n", "hellomwb", cnt--);

    write(fd, outBuffer, strlen(outBuffer));//向文件写入string的时候,要不要+1
  }

  
  close(fd);
}

 

 read

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

#define FILE_NAME  "log.txt"



int main()
{

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

  char buffer[1024];
  ssize_t num = read(fd, buffer, sizeof(buffer) - 1);

  if (num > 0)
  {
    buffer[num] = 0;//0,'\0',NULL 都是0
  }
  printf("%s", buffer);

  
  close(fd);
}

文件描述符fd

进程与被打开文件的关系(理解的关键)

一个进程可以打开很多文件,操作系统中一定有很多被打开的文件操作系统为了管理对应的打开文件必定要为文件创建对应的内核数据结构标识文件struct file{}。里面包含了文件的大部分属性。

task_struct当中有一个指针,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。

当进程打开log.txt文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file连入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可。

见见猪跑

分别打开5个不同的文件,并且打印他们的文件描述符。

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


#define FILE_NAME(number) "log.txt" #number



int main()
{


  int fd0 = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd1 = open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd2 = open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd3 = open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);
  int fd4 = open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);
  
  printf("fd: %d\n", fd0);
  printf("fd: %d\n", fd1);
  printf("fd: %d\n", fd2);
  printf("fd: %d\n", fd3);
  printf("fd: %d\n", fd4);

  close(fd0);
  close(fd1);
  close(fd2);
  close(fd3);
  close(fd4);
  
}

 我们能发现他们是连续的小整数:3 4 5 6 7。

1、为什么他们不是从0开始的?

因为0,1,2已经被占用了。

打印标准输入,标准输出,标准错误对应的文件描述符:

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

int main()
{

  printf("stdin->fd: %d\n",stdin->_fileno);
  printf("stdout->fd: %d\n",stdout->_fileno);
  printf("stderr->fd: %d\n",stderr->_fileno);
}

 2、为什么他们是连续的小整数?

他们其实是一个指针数组 struct file* fd_array[]的数组下标,这个结构体被存在task_struct里面一个指针指向。

3、C语言文件操作函数与Linux下的文件操作函数的关系是什么?

不仅C语言,C++、java、Python等等编程语言,只要在Linux下使用。他们其实都是封装了Linux下的系统调用接口。

 跟操作系统层面的这张图是对应的,其他编程语言中的文件操作函数就在用户操作接口这一层。

fd文件描述符的分配规则

当我们把标准输入(fd=0),标准错误(fd=2)关闭的话会出现什么样的结果呢?(先不关闭1,因为1为标准输出,当我们把1关闭的时候观察不到输出的结果了)

如图所示,当我们关闭0的时候,当我们再试图用open打开一个文件并且打印它的文件描述符的时候,它居然变成了0。

然后我们再尝试着关闭2试一下。 

 同理,我们打开新文件的描述符变成了2。

当然,如果我们关闭1的话是观察不到结果的。

结论

我们文件描述符分配的规则是使用未被占用的最小的那个fd。

重定向

重定向本质就是让本应该输出到一个文件的数据重定向输出到另一个文件中。

输入重定向原理

比如我们需要实现一个让本该输出到显示器的文件数据输出到我们指定的文件当中,那我们就可以在打开一个文件之前把标准输出(fd=1)给关闭,那么我们该文件分配到的就是1的文件描述符。

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

int main()
{
  
  close(1);

  umask(0);
  int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  if (fd < 0)
  {
    perror("open");
    return 1;
  }

  const char *msg = "hello mwb\n";
  write(1, msg, strlen(msg));
  fflush(stdout);
  close(fd);

  return 0;
}

  

原理:

 输出重定向原理

同理,我们想要把从键盘输入的数据从文件中读取,就可以关闭标准输入(fd=0),然后再打开一个文件,在文件里面对标准输入进行写入。(其实写入的就是文件里面的内容)。

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

int main()
{
  close(0);
  

  umask(0);
  int fd = open("log.txt", O_RDONLY);
  if (fd < 0)
  {
    perror("open");
    return 1;
  }

  


  char line[64];
  while (1)
  {
    printf("> ");
    if(fgets(line, sizeof(line), stdin)==NULL) break;//stdin里面读取
    printf("%s",line);
  }

  
  close(fd);

  return 0;
}

 dup2

要完成重定向我们只需进行fd_array数组当中元素的拷贝即可。

dup2函数使用 

也就是说dup2后看似我们使用的是newfd,其实使用的是oldfd,也就是说保留了oldfd。

这里其实很不好理解,我的简单理解就是,让我们使用oldfd的时候其实使用的是newfd的功能。

举例说明:将本该输出到显示屏上的数据显示到文件里面。

#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 | O_TRUNC, 0666);
 
  if (fd < 0)
  {
    perror("open");
    return 1;
  }



  dup2(fd, 1);
  printf("fd: %d\n", fd);
  fprintf(stdout, "fd: %d\n", fd);

  
  close(fd);

  return 0;
}

从输出结果上我们发现,其实dup2和我们之前使用先关闭标准输出(fd=1)是有所不同的,当前文件的fd还是3,但是其实fd对应数组中的内容已经拷贝到1所在位置了,并且完成了重定向的操作。

将重定向功能添加到myshell中

配有详细注释,有意者可看。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#define NUM 1024
#define OPT_NUM 64

//用宏定义出来各个文件操作

//没有特性
#define NONE_REDIR 0

//输入重定向
#define INPUT_REDIR 1

// 输出重定向
#define OUTPUT_REDIR 2

// 追加重定向
#define APPEND_REDIR 3


// 先在此处定义,然后while循环中将每次都对这两个变量重新赋初值
// 重定向的方式
int redirType = NONE_REDIR;
// 需要重定向的文件
char *redirFile = NULL;

//start就是重定向符号后面的字符串
#define trimSpace(start)    \
  do                        \
  {                         \
    while (isspace(*start)) \
      start++;              \
  } while (0)

char lineCommand[NUM];
char *myargv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;


//检查我们输入的命令中是否包含'>' '<' '>>' 其实也就是是否需要进行重定向操作
void commandCheck(char *commands)
{
  //字符串为空直接终止进程报错
  assert(commands);


  // 从左到右扫描
  // 设置两个指针进行遍历操作(其实本质还是找是否有'>' '<' '>>')
  char *start = commands;
  char *end = commands + strlen(commands);

  //便利完成即可退出
  while (start < end)
  {
    // 根据我们遍历到的重定向符号进行相应的操作
    if (*start == '>')
    {
      *start = '\0';
      start++;
      //若一下if成立则说明是追加重定向'>>'
      if (*start == '>')
      {
        // 修改对应的宏来标记对应的重定向操作
        redirType = APPEND_REDIR;
        //继续遍历操作
        start++;
      }
      else
      {
                // 修改对应的宏来标记对应的重定向操作
        redirType = OUTPUT_REDIR;
      }
      trimSpace(start);
      //确定需要操作的文件
      redirFile = start;
      break;
    }
    else if (*start == '<')
    {
      //"cat<file.txt"
      *start = '\0';
      start++;
      trimSpace(start);
      
// 修改对应的宏来标记对应的重定向操作
      redirType = INPUT_REDIR;
      //确定需要操作的文件
      redirFile = start;
      break;
    }
    else
    {
      start++;
    }
  }
}

int main()
{
  while (1)
  {

    // 对重定向的方式和重定向的文件进行初始化
    //  重定向的方式
    redirType = NONE_REDIR;
    // 需要重定向的文件
    redirFile = NULL;

    errno = 0;
    // 输出提示符
    printf("用户名@主机名 当前路径# ");
    // 刷新缓冲区,将printf中打印的值给立即输出出来
    fflush(stdout);

     接受用户输入的字符串
    char *s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
    assert(s != NULL);

    (void)s; // 为使shell不报错,进行强转去掉也无妨

     将我们输入的字符串进行分割

    // 目的是将我们输入指令后敲的那个回车给去掉
    lineCommand[strlen(lineCommand) - 1] = 0;
    // printf("test: %s\n",lineCommand);

    //"ls -a -l -i" -> "ls" "-a" "-l" "-i" -> l -> n

    // 字符串分割之前 要做检查
    // 在我们所输入字符串里面去检查是否有 '>' '>>' '<'
    commandCheck(lineCommand);

    // 字符串切割
    myargv[0] = strtok(lineCommand, " ");

    int i = 1;
     判断是否为ls命令,如果为ls命令 加上颜 色
    if (myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
    {
      myargv[i++] = (char *)"--color=auto";
    }

    // 如果没有子串了,strtok->NULL,myargv[end]=NULL
    // 将切割后的子串一个个赋值给myargv 当切割结束刚好返回NULL
    // while判断条件中先赋值再判断
    while (myargv[i++] = strtok(NULL, " "))
      ;

     如果是cd命令,不需要创建子进程,让shell自己执行对应的命令
    // 本质就是执行系统接口
    // 像这种不需要让我们子进程来执行,而是让shell自己执行的命令 --- 内建/内置命令
    if (myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
    {
      if (myargv[1] != NULL)
        chdir(myargv[1]); // chdir可以直接改变当前进程目录
      continue;           // 直接退出此次while循环,已经不需要再使用子进程了
    }

    if (myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
    {
      if (strcmp(myargv[1], "$?") == 0)
      {
        printf("%d, %d\n", lastCode, lastSig);
      }
      else
      {
        printf("%s\n", myargv[1]);
      }
      continue;
    }

     测试是否成功,条件编译(只是对我们前期简写代码的一个测试)
#ifdef DEBUG
    for (int i = 0; myargv[i]; i++)
    {
      printf("myargv[%d]: %s\n", i, myargv[i]);
    }
#endif

    // 执行命令
    // 使用fork创建子进程来完成我们需要完成的进程程序替换
    pid_t id = fork();
    // 如果id==-1 直接终止程序
    assert(id != -1);

    // 如果id==0则说明是子进程 接下来进行子进程内的进程程序替换
    if (id == 0)
    {
      // 因为命令是子进程执行的,真正重定向的工作一定是子进程来完成的
      // 如何重定向,是父进程要给子进程提供信息的
      // 这里重定向会影响父进程吗?
      // 不会,因为进程具有独立性,这里对文件的操作父进程中fd不会改变。

      switch (redirType)
      {
        // 如果没有重定向,什么都不做就行了
      case NONE_REDIR:
        break;

      case INPUT_REDIR:
      {

        int fd = open(redirFile, O_RDONLY);
        if (fd < 0)
        {
          perror("open");
          exit(errno);
        }
        // 重定向的文件已经打开了
        dup2(fd, 0);
      }
      break;

      case APPEND_REDIR:
      case OUTPUT_REDIR:
      {
        umask(0);
        int flags = O_WRONLY | O_CREAT;
        if (redirType == APPEND_REDIR)
          flags |= O_APPEND;
        else
          flags |= O_TRUNC;
        // 由于这里是输出重定向,如果文件不存在会自己创建,因此我们务必设置文件的权限
        int fd = open(redirFile, flags, 0666);
        if (fd < 0)
        {
          perror("open");
          exit(errno);
        }

        dup2(fd, 1);
      }

      break;

      default:
        printf("bug?\n");
        break;
      }

      // 执行程序替换的时候,会不会影响曾经进程打开的重定向文件
      // 不会 程序替换是在磁盘与物理地址之间进行的操作
      // 进程打开的重定向文件是在进程PCB中的操作,互不干涉
      execvp(myargv[0], myargv);
      // 替换后退出,让父进程能接受到退出码
      exit(1);
    }

    // 父进程在此等待子进程的退出,回收子进程的退出信息
    // 以防没有回收子进程造成僵尸进程
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    assert(ret > 0);
    (void)ret;
    // 我们把退出码和退出信号保存在这两个变量中以便于一会儿我们打印
    lastCode = ((status >> 8) & 0xFF);
    lastSig = (status & 0x7F);
  }
}

 父子进程共有一块文件区域,由于文件区域不属于进程,因此不需要给子进程拷贝一份儿,至于他们指向文件区域如何后续操作,这里需要我们理解写时拷贝!

缓冲区的理解

举例引出缓冲区概念

生活相关

缓冲区类似与我们的外卖寄存柜,当外卖小哥给我们送外卖的时候,我们刚好在忙,外卖小哥不用当时就给我们交互。他可以把我们的外卖存放到寄存柜里面,然后等我们有空了就可以去拿,这样既不影响我们的正常工作,也给外卖员节省了很多时间。

计算机相关 

我们之前讲过,CPU的计算速度(纳秒级别)是极快的,然而磁盘的读取速度(毫秒级别)相对于CPU来说非常慢,如果CPU什么都不干把时间都浪费到去等磁盘将数据读取到内存中,那岂不是效率极低,因此就引入了缓冲区的概念,在内核空间里面有一块内核缓冲区,可供我们的磁盘将数据加载到内存的时候先存放到内核缓冲区中,等CPU来使用。

 

 

 现象解释

 以上代码如果直接执行,我们发现是四条打印内容,可是当我们重定向到log.txt中的时候,log.txt中的内容却与之前不同。我们能很好的观察到C语言的打印接口重复打印了两次,然而系统调用接口只打印了一次。

原因就是C语言缓冲区的存在,当我们fork之后会创建子进程,然而此时我们使用>将所打印的内容重定向到文件log.txt中的时候。会使用全缓冲来进行缓冲区的刷新,全缓冲:等缓冲区满或者关闭fd的时候会刷新缓冲区,然而父子进程会先后退出,然后就把我们文件的内容多拷贝了一份,先后退出进行缓冲区的刷新,造成了打印两份的操作。然而write是系统调用接口,不存在使用C语言层面的缓冲区的概念因此只打印一份。

缓冲区的刷新策略

1、无缓冲(没有缓冲区,对需要写入或者读取数据直接操作)

2、行缓冲(遇到'\n'进行刷新,一行刷新一次)

3、全缓冲(缓冲区满了才会进行刷新)

模拟实现C语言中的fopen、fwrite、fclose、fflush

Makefile

main:main.c myStdio.c
	gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
	rm -f main

myStdio.c

#include "myStdio.h"

FILE_ *fopen_(const char *path_name, const char *mode)
{
    int flags = 0;
    int defaultMode = 0666;

    if (strcmp(mode, "r") == 0)
    {
        flags |= O_RDONLY;
    }
    else if (strcmp(mode, "w") == 0)
    {
        flags |= (O_WRONLY | O_CREAT | O_TRUNC);
    }
    else if (strcmp(mode, "a") == 0)
    {
        flags |= (O_WRONLY | O_CREAT | O_APPEND);
    }
    else
    {
        // TODO
    }
    int fd = 0;

    if (flags & O_RDONLY)
        fd = open(path_name, flags);

    else
        fd = open(path_name, flags, defaultMode);

    if (fd < 0)
    {
        const char *err = strerror(errno);
        write(2, err, strlen(err));
        return NULL; // 为什么打开文件失败会返回NULL
    }
    FILE_ *fp = (FILE_ *)malloc(sizeof(FILE_)); // 申请堆空间
    assert(fp);

    fp->flags = SYNC_LINE; // 默认设置成行刷新
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->size = 0;
    memset(fp->buffer, 0, SIZE);

    return fp; // 为什么你们打开一个文件,就会返回一个 FILE* 指针
}
void fwrite_(const void *ptr, int num, FILE_ *fp)
{
    // 1、写入到缓冲区中
    memcpy(fp->buffer + fp->size, ptr, num); 
    //fp->buffer指向缓冲区的开头+fp->size指向size后,已经使用了size
    //所以接下里的文件应该放在size后面
    //memcpy的意思是将从ptr开始num个数据复制到从fp-buffer+fp->size的位置之后
    // 这里我们不考虑缓冲区溢出的问题
    fp->size += num;
    // 2、判断是否刷新
    if (fp->flags & SYNC_NOW)
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; // 清空缓冲区
    }
    else if (fp->flags & SYNC_FULL)
    {
        if (fp->size == fp->cap)
            write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0;
    }
    else if (fp->flags & SYNC_LINE)
    {
        // abcd\nef不考虑
        if (fp->buffer[fp->size - 1] == '\n')
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    else
    {

    }
}
void fflush_(FILE_ *fp)
{
    if (fp->size > 0)
        write(fp->fileno, fp->buffer, fp->size);
        fsync(fp->fileno); //将数据,强制要求OS进行外设刷新!
        fp->size=0;
}
void fclose_(FILE_ *fp)
{
    fflush_(fp);
    close(fp->fileno);
}

myStdio.h

#pragma once


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

#define SIZE 1024
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL 4

typedef struct _FILE
{
    int flags;// 刷新方式
    int fileno;
    int cap; //buffer的总容量
    int size;//buffer当前的使用量
    char buffer[SIZE];
} FILE_;

FILE_ *fopen_(const char *pathname, const char *mode);
void fwrite_(const void *ptr, int num, FILE_ *fp);
void fclose_(FILE_ *fp);
void fflush_(FILE_ *fp);

main.c

#include "myStdio.h"
#include <stdio.h>

int main()
{
    FILE_ *fp = fopen_("./log.txt", "w");

    if (fp == NULL)
    {
        return 1;
    }
    int cnt = 10;
    const char *msg = "hello mwb ";
    while (1)
    {
        fwrite_(msg, strlen(msg), fp);
        fflush_(fp);
        sleep(1);
        printf("count: %d\n", cnt);
        // if (cnt == 5)
        //     fflush_(fp);
        cnt--;
        if (cnt == 0)
            break;
    }

    fclose_(fp);
    return 0;
}

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

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

相关文章

Linux线程:互斥锁mutex的使用

1. 同步 & 互斥 &#xff08;1&#xff09;同步 多个进程或线程按照一定的执行顺序完成某一任务&#xff1b;如B任务依赖A任务产生的数据。 &#xff08;2&#xff09;互斥 临界资源同一时刻只能被一个进程或线程使用。 2. 临界资源 和 临界区 &#xff08;1&#xff…

feign远程调用原理

目录 一、简介 二、调用流程分析 2.1 添加注解 2.2 Import(FeignClientsRegistrar.class)&#xff0c; 2.3 代理创建流程 2.4 代理调用 一、简介 feign是springCloud全家桶中的远程调用组件&#xff0c;其底层主要依赖于Java的动态代理机制&#xff0c;然后基于http client…

【软件测试】全网火爆,实战Web项目前后台的bug定位(超详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 系统整体架构 Se…

【Java入门合集】第二章Java语言基础(三)

【Java入门合集】第二章Java语言基础&#xff08;三&#xff09; 博主&#xff1a;命运之光 专栏&#xff1a;JAVA入门 学习目标 掌握变量、常量、表达式的概念&#xff0c;数据类型及变量的定义方法&#xff1b; 掌握常用运算符的使用&#xff1b; 掌握程序的顺序结构、选择结…

【微信小程序开发】微信小程序集成腾讯位置项目配置

第一步 进入官网 按照Hello World流程走 腾讯位置服务官网 1、申请密钥 当然没账号的要先注册一个账号 在我的应用里创建一个新的应用&#xff0c;印象中需要小程序ID&#xff0c;去微信开发者工具里面找到自己的小程序ID填入即可 添加key中勾选勾选WebServiceAPI 2、下载S…

锐龙7000PBO温度墙设置

AMD的锐龙7000处理器首发评测大家也都看过了&#xff0c;很多人关心的都是它的性能是否可以超越12代酷睿甚至即将发布的13代酷睿&#xff0c;这方面的测试结果差不多了&#xff0c;但是很多人不知道的是散热问题更需要关注。 在评测中&#xff0c;锐龙9 7950X在拷机时温度达到…

【PCIE体系结构七】数据链路层介绍

&#x1f449;个人主页&#xff1a;highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 参考书籍&#xff1a;PCI_Express体系结构导读 目录 前言 数据链路层概述 数据链路层…

命名ACL配置

命名ACL配置 【实验目的】 掌握命名ACL的配置。验证配置。 【实验拓扑】 实验拓扑如图1所示。 图1 实验拓扑 设备参数如表所示。 表1 设备参数表 设备 接口 IP地址 子网掩码 默认网关 R1 S0/3/0 192.168.1.1 255.255.255.252 N/A Fa0/0 192.168.2.1 255.255.…

05_Uboot源码目录分析

目录 Uboot 源码目录分析 arch 文件夹 board 文件夹 configs 文件夹 .u-boot.xxx_cmd 文件 Makefile 文件 u-boot.xxx文件 .config文件 README Uboot 源码目录分析 学会uboot使用以后就可以尝试移uboot到自己的开发板上了,但是在移植之前需要我们得先分析一遍uboot的…

什么是Spring FactoryBean?有什么作用?

1、什么是Spring Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架&#xff0c;以 IOC和AOP为内核。含有七大核心模块 2、Spring的七大模块 (1)Spring Core&#xff1a;核心容器提供了Spring的基本功能。核心容器的核心功能是用IOC 容器来管理类的依赖关系&#xff…

卷积神经网络详解

&#xff08;一&#xff09;网络结构 一个卷积神经网络里包括5部分——输入层、若干个卷积操作和池化层结合的部分、全局平均池化层、输出层&#xff1a; ● 输入层&#xff1a;将每个像素代表一个特征节点输入进来。 ● 卷积操作部分&#xff1a;由多个滤波器组合的卷积层。 …

788. 逆序对的数量(C++和Python3)——2023.5.2打卡

文章目录 QuestionIdeasCode Question 给定一个长度为 n 的整数数列&#xff0c;请你计算数列中的逆序对的数量。 逆序对的定义如下&#xff1a;对于数列的第 i 个和第 j 个元素&#xff0c;如果满足 i<j 且 a[i]>a[j] &#xff0c;则其为一个逆序对&#xff1b;否则不…

14-6-进程间通信-信号量

前面学习了pipe,fifo,共享内存&#xff0c;信号。 本章将讲述信号量。 一、什么是信号量/信号量集&#xff1f; 1.什么是信号量 信号量是一个计数器。信号量用于实现进程间的同步和互斥。而可以取多个正整数的信号量被称为通用信号量。 对信号量的使用场景的解读 房间&#…

MyBatis学习记录

文章目录 MyBatis介绍JDBC缺点MyBatis简化MyBatis快速入门之查询user表中的所有数据1、创建user表&#xff0c;添加数据2、创建模块&#xff0c;导入坐标3、编写MyBatis核心配置文件 --> 替换连接信息&#xff0c;解决硬编码问题4、编写SQL映射文件 --> 同一管理sql语句&…

计算机网络:DNS域名解析过程

基本概念 DNS是域名系统&#xff08;Domain Name System&#xff09;的缩写&#xff0c;也是TCP/IP网络中的一个协议。在Internet上域名与IP地址之间是一一对应的&#xff0c;域名虽然便于人们记忆&#xff0c;但计算机之间只能互相认识IP地址&#xff0c;域名和IP地址之间的转…

实例解读nn.AdaptiveAvgPool2d((1, 1))

nn.AdaptiveAvgPool2d((1, 1))在PyTorch中创建一个AdaptiveAvgPool2d类的实例。该类在输入张量上执行2D自适应平均池化。 自适应平均池化是一种池化操作&#xff0c;它计算每个输入子区域的平均值并产生一个指定大小的输出张量。子区域的大小是根据输入张量的大小和输出张量的…

5年测试点工?老鸟总结功能到接口自动化测试进阶,自动化核心竞争力...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 我们来说说 功能测…

权限提升:信息收集 .(Linux系统)

权限提升&#xff1a;信息收集. 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动的&#xff0c;因此通过 Webshel…

unity 性能优化之GPU和资源优化

Shader相关优化 众所周知&#xff0c;我们在unity里编写Shader使用的HLSL/CG都是高级语言&#xff0c;这是为了可以书写一套Shader兼容多个平台&#xff0c;在unity打包的时候&#xff0c;它会编译成对应平台可以运行的指令&#xff0c;而变体则是&#xff0c;根据宏生成的&am…

【英语】大学英语CET考试,翻译部分(修饰后置,定语从句,插入语,多动句,无主句)

文章目录 3大知识点与出题形式1、修饰后置&#xff08;使用介词&#xff09;2、修饰后置&#xff08;定语从句&#xff08;被逼无奈&#xff09;/which&#xff08;非限制性&#xff0c;加高级&#xff09;&#xff09;3、修饰后置&#xff08;插入语或同位语&#xff08;只有1…