基础IO(上)

news2024/11/17 7:17:38

基础IO(上)

  • 回顾文件知识
  • 回顾C文件接口
  • 系统文件I/O
    • 接口介绍
      • open
      • close
      • write
      • read
    • 理解文件描述符fd
    • 理解0 1 2 3 4....
    • 文件描述符的分配规则
  • 重定向的本质及相关操作
    • 认识重定向
    • 重定向的具体原理
    • 重定向的操作
    • 追加重定向和输入重定向
      • 追加重定向
      • 输入重定向
  • 缓冲区的理解
    • 什么是缓冲区
    • 为什么要有缓冲区
    • 缓冲区在哪里
    • 刷新策略
    • 奇怪的代码(和子进程相关)

回顾文件知识

1、文件 = 文件内容 + 文件属性(空文件也占据空间,因为文件属性也是占据文件空间的)

2、文件操作 = 文件内容的操作 + 文件属性的操作(有可能,在操作文件的过程中,既改变文件的内容,也改变文件的属性,比如在修改文件内容的时候就改变了文件最新的修改时间和文件的大小)

3、所谓的“打开”文件,究竟在干什么?将文件的属性或者内容加载到内存中(冯诺依曼体系决定,CPU只能内存中对文件进行读写)。

4、是不是所有文件都处于被打开的状态?绝对不是,没有被打开的文件,在那里?存储在磁盘中。

5、所以文件分为两类:打开的文件(内存文件)和磁盘文件

6、通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?fopen、fclose、fread、fwrite… -> 代码 -> 程序 -> 只有当我们的文件程序,运行起来的时候,才会执行相应的代码,然后才是对文件进行真正的相关操作。

总结:所以对文件真正进行操作的是进程

7、所以学习文件就是学习进程和打开文件的关系。

8、什么是当前路径?

进程的当前路径:

下面的cwd(curren work directory)后面的就是进程的当前路径:

image-20221017150841571

9、当我们向文件写入的时候,最终是不是向磁盘写入?是的。磁盘作为硬件,只能被操作系统访问,所有的上层访问硬件,都必须通过操作系统,所以我们C语言中对文件的相关操作函数,其底层都是封装了操作系统提供的文件相关的系统调用。

回顾C文件接口

#include<stdio.h>
#include<unistd.h>
int main()
{
    //1.问:这个文件没有带路径,默认会在哪里形成呢?
    //  答:会在当前路径下形成。当前路径:进程所在的路径(可以通过chdir进行更改当前进程所在路径)
    //2.r,w,r+,w+(注意:此处没有rw,r+和w+都是既读又写,但是w+在文件不存在的时候会默认创建)a(追加写),a+(读写,读是从最开始读,写是追加写)
    //3.文件清:以w方式打开文件的时候,如果文件存在首先进行文件清空操作
    FILE* fp = fopen("log.txt", "r+");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    const char* msg = "hello world";
    int cnt = 1;
    while(cnt < 20)
    {
      fprintf(fp,"%s: %d\n", msg, cnt++);
    }
    fclose(fp);
    return 0;
}

基于文件操作实现简易的cat功能:

#include<stdio.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printf("Usage: %s filename\n", argv[0]);
        return 1;
    }
    FILE* fp = fopen(argv[1], "r");
    if (fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    while (fgets(buffer, sizeof(buffer), fp) != NULL)
    {
        printf("%s", buffer);
    }
    fclose(fp);
    return 0;
}

系统文件I/O

接口介绍

open

image-20221020184404251

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。以|进行分割。
mode: 给文件初始赋予的权限,受umask限制 //通过umask(权限掩码)可以设置新建文件的权限掩码
参数:
	O_RDONLY: 只读打开
	O_WRONLY: 只写打开
	O_RDWR : 读,写打开
			这三个常量,必须指定一个且只能指定一个
	O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
	O_APPEND: 追加写
    O_TRUNC: 如果文件存在并且该文件允许进行写入就将文件清空
返回值:
	成功:新打开的文件描述符
	失败:-1

close

image-20221024091504906

注意:系统传递标记位,是用位图来进行传递的!比如第一个二进制位代表O_RDOLLY(0000 0001),O_WRONLY(0000 0010)。

代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open\n");
        return 1;
    }
    printf("fd:%d\n", fd);
    close(fd);
    return 0;
}

运行结果:

image-20221024081901093

在上面的运行结果中,并没有出现666的权限,出现的是444,实际上这个地方受到了权限掩码umask的 影响,我们可以使用下面的系统调用来修改umask将其改为0即可:

umask

image-20221022213231101

代码:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open\n");
        return 1;
    }
    printf("fd:%d\n", fd);
    close(fd);
    return 0;
}

运行截图:

image-20221024082149777

write

image-20221024082638158

参数详解:
    fd:文件描述符
    buf:要写入字符串缓冲区的地址
    count:要写入字符串的数目

注意:当我们再次运行下面的代码就会出现下面的情况:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open\n");
        return 1;
    }
    printf("fd:%d\n", fd);
    int cnt = 0;
    const char* str = "aaa";
    //注意:这个地方是绝对不能加'\0'的,因为我们用的是vim,vim相当于是记事本,'\0'是C语言上的结束标志,在vim上就是乱码
    while (cnt < 2)
    {
        write(fd, str, strlen(str));
        cnt++;
    }
    close(fd);
    return 0;
}

查看log.txt:

image-20221024092757624

文件并没有清空,是因为我们open函数中没有加O_TRUNC这个选项对原来的文件进行清空,加上之后就会出现下面的清空:

int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//这三个选项,相当于C语言中fopen的'w'

image-20221024092927697

此外:

O_WRONLY | O_CREAT | O_APPEND //相当于fopen的'a',在文件末尾进行追加

read

image-20221024093829594

参数详解:

参数:
	fd:文件描述符
    buf:要写入的区域
    count:想要读入的字符数
返回值:
    实际读入的字符数//注意:ssize_t是有符号整数

代码练习:

Test.c文件代码:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    umask(0);
    int fd = open("log.txt", O_RDONLY);
    if (fd < 0)
    {
        perror("open\n");
        return 1;
    }
    printf("fd:%d\n", fd);
    char buf[64];
    ssize_t s = read(fd, buf, sizeof(buf) - 1);//为什么要预留一个位置?因为要让给'\0'
    if (s > 0)
    {
        buf[s] = '\0';
        printf("%s", buf);
    }
    close(fd);
    return 0;
}

log.txt文件:

image-20221024094905351

运行截图:

image-20221024095033906

理解文件描述符fd

首先先进行一个实验:

代码:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
  umask(0);
  int fd1 = open("log1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd2 = open("log2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd3 = open("log3.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  int fd4 = open("log4.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  printf("%d %d %d %d\n", fd1, fd2, fd3, fd4);
  close(fd1);
  close(fd2);
  close(fd3);
  close(fd4);
  return 0;
}

运行截图:

image-20221024102435751

此时查看open的返回值描述:

image-20221024102553844

如果返回-1就说明错误发生了,只有当fd>=0才说明文件打开成功。

为什么从3开始?0 1 2是什么?

0 1 2被默认打开了:

0:标准输入:键盘

1:标准输出:显示器

2:标准输出:显示器

上面的可以类比C语言中的stdin,stdout,stderr

两者有什么区别呢?0 1 2是针对系统接口的概念,而stdin,stdout,stderr是C语言中的概念。

C语言中有FILE*类型的文件指针,那么FILE是什么呢?FILE是一个结构体,封装了很多成员,其中就封装了fd。

下面进行验证:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
//先验证0 1 2就是标准IO
char buffer[1024];
ssize_t s = read(0, buffer, sizeof(buffer)- 1);//从标准输入中读数据到buffer中
if(s > 0)
{
 buffer[s] = '\0';
 printf("echo:%s", buffer);
}
return 0;
}

运行截图:

image-20221024104622830

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
const char* str = "hello world\n";
write(1, str, strlen(str));//将str字符串中的内容写到标准输出(stdout)中去
write(2, str, strlen(str));//将str字符串中的内容写到标准错误(stderr)中去
return 0;
}

image-20221024105112972

下面再次进行验证stdin stdout stderror和0 1 2之间的对应关系:

代码:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
printf("stdin:%d\n", stdin->_fileno);
printf("stdout:%d\n", stdout->_fileno);
printf("stderr:%d\n", stderr->_fileno);
return 0;
}

image-20221024105750404

总结:

函数接口的对应:

fopen/fwrite/fread… -> open/write/read/…

数据类型的对应:

FILE -> fd

理解0 1 2 3 4…

一个进程可以打开文件,包括标准输入、标准输出、标准错误还有其它文件(打开的文件在内存中),进程 : 打开的文件 = 1 : n ,所以系统在运行中有大量被打开的文件,OS要对这些文件进行管理,所以就要先描述后组织。所以一个文件被打开,在内核中就要创建该被打开的文件的内核数据结构,这是描述的过程。

struct file
{
    //包含了文件的大部分内容和属性
    struct file* next;
    struct file* prev;
    //只是为了方便描述,实际上内核数据结构有自己的链接方式和结构,但是相互链接是确定的
}

image-20221024114542771

理解Linux下一切皆文件

首先先理解一下C语言是如何实现面向对象中的多态的?

image-20221024135921419

一切皆文件的理解:

image-20221024141940325

注意:磁盘、显示器、键盘、网卡等的read和write的具体方法是驱动负责的。

注意:OS内的文件系统也叫做VFS,即虚拟文件系统。

我们可以通过ulimit -a指令来查看打开文件的个数:

image-20221024143202456

文件描述符的分配规则

从头遍历fd_array数组,找到一个最小的且没有被使用的下标分配给新的文件

代码:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
  close(0);
  int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  if(fd < 0)
  {
    perror("open\n");
    return 1;
  }
  printf("fd:%d\n", fd);
  close(fd);
}

运行截图:

image-20221024144654507

重定向的本质及相关操作

认识重定向

下面以一个小例子来了解重定向:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
  close(1);
  int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
  //此时fd等于1,即文件log.txt的文件描述符是1,stdout默认就是1,所以此时如果我们向stdout中进行输出,其实是输出到了文件log.txt中
  if(fd < 0)
  {
    perror("open\n");
    return 1;
  }
  //本来应该要往显示器打印,最终变成了向log.txt文件中打印
  printf("fd:%d\n", fd);
  fflush(stdout);//刷新缓冲区
  close(fd);
  return 0;
}

运行截图:

image-20221024164002340

image-20221024163658882

如果我们要进行重定向,上层只任0,1,2,3,4,5这样的fd,我们可以在OS内部,,通过这一方式调整数组的特定下标的内容(指向),我们就可以完成重定向操作。

总结:本来应该向显示器打印,最终变成了向指定文件打印,这就是重定向

重定向的具体原理

image-20221024165501034

如果我们要进行重定向,操作系统只认识0 1 2 3…这样的fd,我们可以在OS内部,通过一定的方式调整数组特定下标的内容(指向),我们就可以完成重定向操作

在上面的例子中,就像下面这样进行改变:

image-20221024165622477

printf是向1(log.txt文件)中进行写入,操作系统仍然认为1是stdout,所以程序运行的结果是向log.txt文件中进行写入。

重定向的操作

image-20221024170522850

dup2作用:让新的fd称为旧的fd的拷贝,最终只剩下了旧的fd,如果必要的话,新的fd会被关闭

注意:拷贝的不是数组下标,而是相应的数组下标所存储的内容,即struct file*。

image-20221024173239706

返回值

image-20221025103832592

返回的是新的,即new文件描述符

问:一个文件是怎么做到被打开多次的呢?

答:是通过类似引用计数的方式,当被打开一次的时候,引用计数就+1,close之后,引用计数就-1,当引用计数变成0的时候,文件才会彻底关闭。

追加重定向和输入重定向

追加重定向

代码:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
  int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);//把O_TRUNC换成O_APPEND即可实现追加重定向
  if(fd < 0)
  {
    perror("open\n");
    return 1;
  }
  dup2(fd, 1);
  printf("fd:%d\n", fd);
  fflush(stdout);
  close(fd);
  return 0;
}

运行截图:

image-20221025105910554

输入重定向

代码:

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
  int fd = open("log.txt", O_RDONLY);
  if(fd < 0)
  {
    perror("open\n");
    return 1;
  }
  dup2(fd, 0);//输入重定向
  char str[64];
  while(fgets(str, sizeof(str) - 1, stdin))
  {
    printf("%s", str);
  }
  close(fd);
  return 0;
}

运行截图:

image-20221025111012382

缓冲区的理解

什么是缓冲区

缓冲区的本质:就是一段内存。

为什么要有缓冲区

  • 解放当前使用缓冲区的进程的时间(即解放当前进程的时间,因为如果当前进程要直接将数据传输到外设中的话,,这个过程要花费很多的时间,且无法处理后面的代码)
  • 缓冲区的存在可以集中处理数据刷新,减少IO的次数,从而达到提高整机的效率

缓冲区在哪里

代码测试:

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

int main()
{
  printf("hello printf\n");
  const char* str = "hello write\n";
  write(1, str, strlen(str));
  sleep(5);
  return 0;
}

sleep前:

image-20221025114148893

sleep后:

image-20221025114544287

将上面的代码进行如下修改:

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

int main()
{
  printf("hello printf");
  const char* str = "hello write";
  write(1, str, strlen(str));
  sleep(5);
  return 0;
}

运行截图:

sleep前:

image-20221025114428708

sleep后:

image-20221025114456027

分析:首先printf封装了write,write是立即刷新的,即一旦有数据传给write就会立即在显示器上打印出来,hello printf没有先显示的原因是被存放到了缓冲区中,缓冲区没有被刷新,即缓冲区中的数据没有被传给write函数,所以没有被打印出来,当进程结束的时候,会将数据直接传给write函数,数据被立即刷新,所以最后hello printf被打印了出来。

image-20221025123701624

总结:缓冲区在哪里呢,只能是由特定的语言提供的stdout的类型是FILE类型的,缓冲区就封装在这个结构体中缓冲区不是内核级别的。缓冲区在FILE内部,在C语言中,每一次打开一个文件,都要有一个FILE*会返回,意味着每一个文件都有一个fd和属于它自己的语言级别的缓冲区。所以open函数在打开文件的时候创建了一个FILE类型的结构体对象。

//在 / usr / include / libio.h
struct _IO_FILE {
	int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr; /* Current read pointer */
	char* _IO_read_end; /* End of get area. */
	char* _IO_read_base; /* Start of putback+get area. */
	char* _IO_write_base; /* Start of put area. */
	char* _IO_write_ptr; /* Current put pointer. */
	char* _IO_write_end; /* End of put area. */
	char* _IO_buf_base; /* Start of reserve area. */
	char* _IO_buf_end; /* End of reserve area. */
	/* The following fields are used to support backing up and undo. */
	char* _IO_save_base; /* Pointer to start of non-current get area. */
	char* _IO_backup_base; /* Pointer to first valid character of backup area */
	char* _IO_save_end; /* Pointer to end of non-current get area. */
	struct _IO_marker* _markers;
	struct _IO_FILE* _chain;
	int _fileno; //封装的文件描述符
#if 0
	int _blksize;
#else
	int _flags2;
#endif
	_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
	unsigned short _cur_column;
	signed char _vtable_offset;
	char _shortbuf[1];
	/* char* _save_gptr; char* _save_egptr; */
	_IO_lock_t* _lock;
#ifdef _IO_USE_OLD_IO_FILE
};

问:在上面的例子中,如果关闭了1会发生什么情况?

答:

代码:

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

int main()
{
    printf("hello printf");
    const char* str = "hello write";
    write(1, str, strlen(str));
    close(1);
    sleep(5);
    return 0;
}

运行截图:

image-20221025124713529

image-20221025124728382

问:为什么会出现这样的情况呢?

答:因为下标1的file*指针已经不再指向stdout的file结构体了,自然无法找到之前的缓冲区然后调用write去刷新数据了。

刷新策略

什么时候刷新?

常规:

  • 无缓冲(立即刷新)
  • 行缓冲(逐行刷新):显示器文件
  • 全缓冲(缓冲区写满才刷新):块设备(磁盘文件)

特殊情况:

  • 进程退出
  • 用户强制刷新(fflush)

注意:exit()和_exit()的区别,exit()会刷新缓冲区,_exit会清空缓冲区。

奇怪的代码(和子进程相关)

代码:

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

int main()
{
  const char* str1 = "hello printf\n";
  const char* str2 = "hello fprintf\n";
  const char* str3 = "hello fputs\n";
  const char* str4 = "hello write\n";
  //C库函数
  printf("%s", str1);
  fprintf(stdout, "%s", str2);
  fputs(str3, stdout);
  //系统接口
  write(1, str4, strlen(str4));
  //执行完上面的代码,才执行的fork
  fork();
  return 0;
}

运行截图:

image-20221025143637363

如果我们通过重定向改为向文件中进行写入:

image-20221025143730547

两次的结果是不一样的。

首先先明确:向显示器写入的刷新策略是行缓冲,向文件进行写入的刷新策略是全缓冲。

刷新的本质把刷新区的数据write到OS内部,然后清空缓冲区,注意:清空缓冲区时就对缓冲区中的数据进行了修改

缓冲区是自己的FILE内部进行维护的,属于父进程的数据区域。所以清空缓冲区时,必然发生了写时拷贝。那么无论子进程还是父进程先

退出,都相当于有了两份缓冲区中的数据,都会向文件中进行写入,所以最终输出结果有两份。

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

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

相关文章

C++ STL源码剖析 笔记补充

写在前面 简单记录一些《C STL源码剖析中》涉及到的C语法和注意事项。 1. 静态常量成员在类内直接初始化 如果含有const static integral类型的成员变量&#xff0c;可以在类内定义时直接初始化&#xff1b; 注意integral不只是int类型&#xff0c;而是包含所有的整型&#…

< 每日算法 - Javascript解析:经典弹珠游戏 >

每日算法 - JavaScript解析&#xff1a;弹珠游戏一、任务描述&#xff1a;》 示例一&#xff1a;》示例二二、题意解析三、解决方案&#xff1a;往期内容 &#x1f4a8;一、任务描述&#xff1a; 欢迎各位来到「力扣嘉年华」&#xff0c;接下来将为各位介绍在活动中广受好评的…

HSAF实战收获

收获1&#xff1a;MySQL数据类型对应Java类型表格这里的timestamp类型在Java中对应TimeStamp类型&#xff0c;varchar和char都是对饮的String类型收获2&#xff1a;TableFieldTableField(exist false) 注解加载bean属性上&#xff0c;表示当前属性不是数据库的字段&#xff0c…

[golang Web开发] 4.golang web开发:模板引擎

一.简介 使用 Go 的 Web 模板引擎需要以下两个步骤&#xff1a; (1).对文本格式的模板源进行语法分析&#xff0c;创建一个经过语法分析的模板结构&#xff0c;其中模板源既可以是一个字符串,也可以是模板文件中包含的内容 (2).执行经过语法分析的模板&#xff0c;将ResponseWr…

Django User模型

Django User模型用户管理自定义用户模型Django自定义验证引用User模型视图开发创建序列器创建视图创建路由用户注册注册序列化器注册视图注册路由用户管理 在开发登录功能的时候需要数据库来保存用户登录信息和状态&#xff0c;Django中有个内置app 名为 django.contrib.auth …

ICT是什么

信息与通信技术&#xff08;ICT&#xff0c;information and communications technology&#xff09;是一个涵盖性术语&#xff0c;覆盖了所有通信设备或应用软件&#xff1a;比如说&#xff0c;收音机、电视、移动电话、计算机、网络硬件和软件、卫星系统&#xff0c;等等&…

(1)Nginx简介和安装教程

目录 一、下载 二、报错提醒&环境安装 1、安装gcc编译器 2、安装perl库 3、安装 zlib库 4、也可通过命令进行统一安装 三、编译及安装 四、启动并访问 1、启动 2、访问 3、问题排查 五、安装成系统文件 一、下载 官网地址&#xff1a;nginx news Nginx官网提供…

OAuth2入门

1.下载资源 演示代码&#xff1a; OAuth2-example: 演示OAuth2的认证流程https://gitee.com/lisenaq/oauth2-example克隆下载到本地&#xff1a; 导入项目&#xff1a; client 客户 authorization-server 认证服务 resource-owner 资源所有者 resource-server 资源…

儿童台灯哪个品牌更护眼推荐?儿童书桌台灯品牌排行榜

不难发现&#xff0c;近些年我国儿童近视率增长迅速&#xff0c;随着生活条件越来越好&#xff0c;对电子章产品的普及非常广泛&#xff0c;每个家庭的孩子必不可少的就是伏案完成作业&#xff0c;这样的话就需要使用到台灯&#xff0c;选购台灯的时候最好选择适合儿童的专业护…

【算法基础】高精度加法

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前是C语言学习者 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞…

一篇五分生信临床模型预测文章代码复现——FIgure 9.列线图构建,ROC分析,DCA分析 (三)

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…

axios实战学习——使用高德地图api接口数据实现天气查询案例

文章目录&#x1f4cb;前言&#x1f3af;案例介绍&#x1f9e9;关于高德开发平台1️⃣创建应用生成Key2️⃣查看API文档&#x1f9e9;测试接口&#x1f3af;案例编写&#x1f3af;实现效果&#x1f4cb;前言 关于这个Vue axios 获取接口数据的操作&#xff0c;这篇文章就不过…

UniRx之基础入门

什么是Rx 官方ReactiveX简介&#xff1a; An API for asynchronous programming with observable streams。 通过这句话我们可以得到&#xff1a; 1.首先Rx是个编程接口&#xff0c;不同语言提供不同实现。例如JVM语言中的RxJava。 2.使用场景&#xff0c;异步编程中。 3.基…

路由器 内核开发 流程

宽 带上网已经不是什么新鲜事情&#xff0c;人们对相关的网络器件已经不再陌生&#xff0c;比如说常见的路由器。对于一般的网络用户&#xff0c;他们能知道怎样使用路由器来上网、玩游戏等就 已经感到很满足了&#xff0c;通常情况下对路由器的深层技术很少去过问研究&#xf…

Matlab和PCL中的点云滤波

而在PCL中总结了几种需要进行点云滤波处理的情况&#xff0c;这几种情况分别是&#xff1a; (1)点云数据密度不规则需要平滑。 (2)因为遮挡等问题造成离群点需要去除。 (3)大量数据需要进行“下采样”(Downsample)。 (4)噪声数据需要去除。 对应的解决方法是&#xff1a; (1)按…

什么是ITIL中的变更管理

商业环境和客户期望在不断变化&#xff0c;数字化转型已成为各行各业企业成功的关键因素。数字化转型的关键在于利用可用 应对业务挑战和抓住机遇的技术。当你分解它时&#xff0c;数字化转型基本上是信息技术管理更好地消除有问题的领域&#xff0c;并使您的 IT 基础架构能够应…

WebView缓存机制

一 前言 由于H5具备 开发周期短、灵活性好 的特点&#xff0c;所以现在 Android App大多嵌入了 Android Webview 组件进行 Hybrid 开发 但我知道你一定在烦恼 Android Webview 的性能问题&#xff0c;特别突出的是&#xff1a;加载速度慢 & 消耗流量 今天&#xff0c;我将…

Django 模型的继承

Django 模型的继承项目管理模型关联关系模块的包管理模型的继承项目管理 到目前为止&#xff0c;都是属于httprunner中的用例部分&#xff0c;针对核心功能进行的开发工作&#xff0c;要把平台做成一个用户可以使用的程度还需要些额外的功能&#xff0c;比如项目管理&#xff…

13薪| 8k-14k Java开发工程师

"众推职聘”以交付结果为宗旨的全流程化招聘服务平台&#xff01;今日招聘信息↓工作内容1.参与软件项目和产品概要设计&#xff0c;负责详细功能设计、编码实现及相关文档编写&#xff1b;2.根据模块设计完成相应的模块编码及单元测试&#xff1b;3.对用户行为、需求及反…

zeek集群简述

Zeek不是多线程的&#xff0c;因此&#xff0c;一旦达到单处理器内核的限制&#xff0c;当前唯一的选择就是将工作负载分散到多个内核&#xff0c;甚至多个物理计算机上。Zeek的集群部署场景是构建这些大型系统的当前解决方案。Zeek附带的工具和脚本提供了一种结构&#xff0c;…