【Linux】一文看懂基础IO并模拟实现

img

Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。

本篇导航

  • 0. C语言的文件接口
  • 1. 系统的文件接口
    • 1.1 open打开文件
    • 1.2 write写入文件
  • 2. 文件系统介绍
    • 2.1 如何理解一切皆文件?
  • 3. 输入输出重定向
  • 4. 用户缓冲区与系统缓冲区
  • 5. 实现Stdio.h

在这里插入图片描述

0. C语言的文件接口

我们在C语言时已经学过了文件调用的相关接口.来复习一下相关接口:

  1. 通过fopen来打开一个文件

image-20231122190650694

其包含在stdio.h的头文件当中.常用的有两种模式: w,a(清空再写入文件末尾进行追加写)

使用方法

#include<stdio.h>
int main()
{
    const char* path="./log.txt";
    FILE* f=fopen(path,"a");
    fclose(f);
    return 0;
}

如果当前工作目录(CWD)下没有该文件,则会创建并打开.若已存在,则直接打开

  1. 通过fwrite来对一个文件对一个文件进行读写

image-20231122193437468

向文件FILE stream写,从ptr处获取内容写,每次写nmemb * size 大小的内存.*

#include<stdio.h>
#include<string.h>
int main()
{
    const char* path="./log.txt";
    FILE* f=fopen(path,"a");
    const char* info="hello,linux\n";
    fwrite(info,strlen(info),1,f);
    fclose(f);
    return 0;
}

注意!!这里写入时strlen并不需要加一,也就是不需要将’\0’算上,因为’\0’是C语言用来分割字符串的方式,但这里是文件进行读写.所以并不需要加上’\0’的地址.

这样我们就成功向log.txt中写入了"hello,linux";

image-20231122194340298

1. 系统的文件接口

复习完C语言的文件调用接口,我们来学习一下系统的文件调用接口.

因为C语言的文件调用接口需要访问硬件资源,在很早之前谈操作系统的时候说过,系统并不相信任何人,如果需要访问其底层的硬件,只能通过系统级的接口来访问.类似的接口有fprint,printf,sprint等等

所以C语言的文件调用接口一定是对系统的接口进行了二次封装.

1.1 open打开文件

和C语言的fwrite大同小异.一样需要传入创建文件的路径 pathname, 之后需要传入flags操作符.再传入创建文件的权限信息mode(使用八进制).

flags 有以下几种:

  • O_APPEND – 追加写
  • O_CREAT – 如果文件不存在就创建
  • RDONLY – 只读
  • O_WRONLY – 只写
  • O_TRUNC – 先清空后写入.

这每个flag都是定义的宏,可以使用|的方式,实现传入多个选项.(每个bit位不同可以代表不同的功能)

其返回值为一个整形:file description 文件描述符.

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    const char* path="./log.txt";
    FILE* f=fopen(path,"w");
    const char* pathos="./logos.txt";
    int fd=open(pathos,O_CREAT|O_WRONLY|O_TRUNC,0666);
    close(fd);
    return 0;
}

所以,C语言中的W权限是对O_CREAT|O_WRONLY|O_TRUNC,进行的一个封装.

image-20231122200515866

由于此时系统中的umask为0002,所以对于group仅有可读权限.

1.2 write写入文件

image-20231122195015122

这个使用方法与fwrite几乎一样.

就不介绍了

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    const char* pathos="./logos.txt";
    int fd=open(pathos,O_CREAT|O_WRONLY|O_TRUNC,0666);
    write(fd,pathos,strlen(pathos));
    close(fd);
    return 0;
}

即使我们写入了多次,文件中也仅存在一条内容.

image-20231122200934064

也可以说明,C语言中的W权限是对O_CREAT|O_WRONLY|O_TRUNC,进行的一个封装.

2. 文件系统介绍

那么上面提到的file description是什么呢? 为什么C语言使用了FILE*的结构体指针作为返回值而底层仅需要返回一个数字?这个数字是什么?

操作系统想要管理被进程已经打开的文件,肯定需要经过先描述再组织这一过程.

我们今天仅把视角放在进程Task_Struct如何管理自己已经打开的文件.而不去关心这个操作系统如何去管理整个文件系统的.

4e7e442d7cca1fe7e9cb0d0e15c3355

进程PCB中有一个管理已经打开的文件系统的指针.管理了一系列已经打开的文件.所以我们得到的整数返回值fd,就是我们当前文件被放在进程已经打开file_struct数组中的下标,我们通过这个下标就可以访问我们刚刚打开的文件,

文件fd每次都会从未用过的下标中的最小值开始分配.通常我们拿到的一般是3.

因为012都被系统中的 0. 标准输入 stdin, 1. 标准输出 stdout, 2. 标准错误 stderr分配了. 所以这三个标准输入输出流是系统的标准.

此外,虽然标准输出与标准错误的结果都是打印在屏幕上(共享屏幕这个资源),但关闭其中一个并不会影响另外一个.

因为对屏幕这个资源采用了引用计数(系统中很多共享资源都采用这个方案),关闭其中一个引用对象,只会导致引用计数–

可以来验证下这个结论.

我们可以直接向fd为1的文件写入数据,看看是否会出现在屏幕上

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    const char* pathos="./logos.txt";
    write(1,pathos,strlen(pathos));
    return 0;
}

image-20231122203402445

我们将其关闭,重新分配文件描述符看是否为1

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    close(1);
    const char* pathos="./logos.txt";
    int fd=open(pathos,O_CREAT|O_WRONLY|O_TRUNC,0666);
    fprintf(stderr,"fd is %d\n",fd);
    close(fd);
    return 0;
}

image-20231122204013093

这里用到了fprintf这个函数

image-20231122204230962

以特定格式向文件中写入内容.

2.1 如何理解一切皆文件?

我们需要有一套统一的方法去操控各样的硬件.

所以在设计操作系统时封装了三层.

  1. 最底层为对各种硬件进行描述,写操作的方法.

  2. 但是每一种硬件肯定都是不一样的.所以有了第二层.统一将每一种操纵每一种文件的方法进行了封装.封装函数指针指向第一层的具体方法,此时他们的名字都一样了

  3. 第三层就是暴露给用户的STRUCT_FILE里面封装了指向第二层具体操作方法,和其他描述这个文件的信息.

这就是C++中多态的设计灵感

fe362b05215d724d3cb8f8d3b5216a8

3. 输入输出重定向

我们前面介绍过,stdout的fd是1.如果我先将1关闭,之后打开一个文件,再向1中写入会发生什么呢?

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    close(1);
    const char* pathos="./logos.txt";
    int fd=open(pathos,O_CREAT|O_WRONLY|O_TRUNC,0666);
    write(1,pathos,strlen(pathos));
    close(fd);
    return 0;
}

原本应该打印在屏幕上的信息,被写到了文件当中

image-20231125111603057

因为stdout先被关闭了,文件描述符的分配是从最小值开始,所以分配时又将1分配给了新的文件,所以再次向1中写入的时候就变成了向文件当中写入

这就是输入输出重定向的本质概念:将输入输出描述符分配给对应的文件,就可以达成输入输出重定向的原理

image-20231125112218475

我们也有一个系统调用接口dup2.将oldfd复制到newfd.

这里可能有点难理解,oldfd可以理解为src(原文件描述符),newfd可以理解为tar(目标文件描述符).把在原文件描述符里指向原文件的指针复制覆盖到目标文件描述符当中

66782e9db8a6fe19f6249844a477065

我们可以使用下面这份代码来实现上面的效果

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
    const char* pathos="./logos.txt";
    int fd=open(pathos,O_CREAT|O_WRONLY|O_TRUNC,0666);
    dup2(fd,1);
    close(fd);
    write(1,pathos,strlen(pathos));
    return 0;
}

在重定向完就可以关闭原来分配到的描述符,也就是close(fd)

image-20231125113529579

所以我们之前在命令行中输入的

command < file
command >> file
command > file

都是一种重定向.

第一种是将输入流重定向到文件当中,也就是从文件当中读取内容放到命令中执行

二三种都是将文件的输出结果写入到文件当中而不是输出到屏幕上.

4. 用户缓冲区与系统缓冲区

要了解缓冲区这个概念,我们先来回顾一个概念.

之前提到过,计算机绝大多数都满足冯诺依曼这个体系,也就是数据参与计算(使用CPU之前),都需要将自己先放入到内存.

因为CPU运行速度远高于其他介质,所以需要从其他"矮个子里挑高个",找一个较快访问速度的存储介质.

那么我们数据原来都是在磁盘里存储的,要想将他写入到内存当中去**.首先面临一个问题,我是一有数据就写,还是等数据到达了一定规模在写进去呢?答案显然是后者**,因为这样仅需拷贝一次,而不需要多次拷贝.

缓冲区充当的就是存储还未到达一定规模的数据,并格式化用户输入的数据.

那么在我们日常使用的语言与系统中 缓冲区在哪里呢?

我们先来看看下面这个现象

#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    const char * buff="hello linux\n";
    
    printf("hello io\n");
    fprintf(stdout,"hello linux i am %d\n",getpid());
    write(1,buff,strlen(buff));

    return 0;
}

内容会正常的被打印出来.

image-20231126125405142

如果这个改成这样

#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    const char * buff="hello linux";
    
    printf("hello io");
    fprintf(stdout,"hello linux i am %d",getpid());
    write(1,buff,strlen(buff));
    close(1);
    return 0;
}

在进程结束前手动关掉标准输出流stdout,去掉了’\n’,结果会怎样呢?

image-20231126130734877

竟然只输出了write里的内容.

如果给其加上’\n’呢?

#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    const char * buff="hello linux\n";
    
    printf("hello io\n");
    fprintf(stdout,"hello linux i am %d\n",getpid());
    write(1,buff,strlen(buff));
    close(1);
    return 0;
}

image-20231126130901900

我们发现,C语言的接口,不加’\n’是不会被刷新的.

C语言有自己的缓冲区(用户缓冲区),在该缓冲区刷新之前不会被写入到系统缓冲区中.

该缓冲区的刷新策略为:

  • 立即刷新 (成本较高)
    • 行刷新 向屏幕打印通常是这个标准
  • 全刷新(满了再刷新) 向文件输入通常是这个标准

进程退出前会刷新C语言的缓冲区.所以当去掉’\n’时,先关闭了stdout,此时未被刷新.所以即使在进程退出时刷新缓冲区,也不会再输出到屏幕上了.

关于系统的缓冲区他有一套自己的规则,我们现在仅需认为,刷新到了系统中的缓冲区,内容就会被写到文件上

有一个fflush接口,是手动刷新该进程的用户缓冲区.

image-20231126132057277

也有一个fclose

image-20231126132143841

我们如果把代码改写成这样

#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    const char * buff="hello linux\n";
    
    printf("hello io\n");
    fprintf(stdout,"hello linux i am %d\n",getpid());
    fclose(stdout);
    return 0;
}

image-20231126132244480

结果就会被正确的打印出来

所以 fclose的本质是 fflush+close.刷新了用户层的缓冲区.

d4520d66a5271945285dd39c0375911

FILE*来维护C中的缓冲区空间.所以,每个被打开的文件都有一段属于自己的缓冲区在FILE中存着

最后再来看看这个现象.

#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    const char * buff="hello linux\n";
    FILE* f=fopen("./helloio.txt", "w");
    dup2(f->_fileno, 1);
    printf("hello io\n");
    fprintf(stdout,"hello linux i am %d\n",getpid());
    write(1,buff,strlen(buff));
    fork();
    return 0;
}

创建了一个子进程,对输出流进行了 重定向,将输出流变为对文件写入.

结果是:

image-20231126133329564

write中的内容被写入了一遍.C接口中被写入两遍

这是因为 进程创建时会复制父进程的所有状态,所以将C语言的缓冲区也拷贝了过来,之前提到过,对普通文件进行写入时,为全刷新,所以即使有’\n’也不会立即刷新缓冲区.而是等待进程退出时再一次刷新.对C缓冲区进行刷新时发生了写时拷贝,父子进程各自拥有一个缓冲区(因为对共享的数据进行了改变),所以被刷新了两次到系统的缓冲区

所以,我们可以大概理解了 C语言的IO接口的底层是什么样的

  1. FILE*内存了fd与描述缓冲区的相关指针
  2. fopen = open + malloc(FILE)
  3. fclose = fflush +close
  4. flush = write
  5. fwrite = write

那么我们就可以自己来实现一个简易版的stdio.h了

5. 实现Stdio.h

Makefile:

test:main.c Mystdio.c
	gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
	rm -rf test

Mystdio.h

#pragma once

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

#define SIZE 1024
#define MODE 0666
#define MALLOC_FAILED 2

//刷新策略  立即刷新 行刷新 全刷新
#define NOW_FLUSH 1
#define LINE_FLUSH 2
#define ALL_FLUSH 4

typedef struct _FILE{
    int fileno;
    char buffer[SIZE];
    int end_pos;
    int flush_mode;
}_FILE;

_FILE* _fopen(const char * path,const char * mode);

size_t _fwrite(const void *ptr, size_t len, _FILE *stream);

int _fclose(_FILE* stream);

int _fflush(_FILE* stream);

Mystdio.c:

#pragma once
#include "Mystdio.h"

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


_FILE* _fopen(const char * path,const char * mode)
{
    if(path==NULL)return NULL;
    
    int fd = 0;
    
    if(strcmp(mode,"w") == 0)
        fd = open(path, O_WRONLY | O_CREAT | O_TRUNC ,MODE);
    else if(strcmp(mode,"a") == 0)
        fd = open(path,O_WRONLY | O_APPEND | O_CREAT, MODE);
    else if(strcmp(mode,"r") == 0)
        fd = open(path,O_RDONLY );
    else return NULL;
    
    if(fd == -1)return NULL;

    _FILE* f = (_FILE*)malloc(sizeof(_FILE));
    
    if(f == NULL)
    {
        perror("malloc failed: ");
        exit(MALLOC_FAILED);
    }

    f->fileno=fd;
    
    f->end_pos=0;
    
    //默认刷新为行刷新
    f->flush_mode=LINE_FLUSH;

    return f;

} 

size_t _fwrite(const void *ptr, size_t len, _FILE *stream)
{
    stream->end_pos += len;
    memcpy(stream->buffer,(char *)ptr,len);

    if(stream->flush_mode & NOW_FLUSH)
    {
        write(stream->fileno,stream->buffer,stream->end_pos);
        stream->end_pos=0;   
    }
    else if(stream->flush_mode & LINE_FLUSH)
    {
        if(stream->buffer[stream->end_pos - 1] == '\n')
        {
                write(stream->fileno,stream->buffer,stream->end_pos);
                stream->end_pos=0;
        }
    }
    else if(stream->flush_mode & ALL_FLUSH)
    {
        if(stream->end_pos == SIZE)
        {
                write(stream->fileno,stream->buffer,stream->end_pos);
                stream->end_pos=0;
        }
    }
    return len;

} 
int _fflush(_FILE* stream)
{
    if(stream == NULL)return -1;
    if(stream->end_pos > 0)
    {
        write(stream->fileno,stream->buffer,stream->end_pos);
        stream->end_pos=0;
    }
    return 0;
} 

int _fclose(_FILE* stream)
{
    if(stream == NULL)return -1;
    _fflush(stream);
    close(stream->fileno);
    free(stream);
    stream=NULL;
} 

image-20230905164632777

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

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

相关文章

Pyqt5设计师中如何插入图片

问题描述&#xff1a;Pyqt5设计师中如何插入图片。使用Pyqt5做一个示意图界面&#xff0c;是一个”假界面“。 问题解决&#xff1a; 第一步&#xff0c;从Widget Box中拖入一个Label&#xff0c;具体如下图所示。 第二步&#xff0c;在右侧属性编辑器→QLabel→pixmap中选择…

从 CoT 到 Agent,最全综述来了!上交出品

就在前两天&#xff0c;我们刚刚和大家聊了聊最近相当火爆的 AI Agents 这一概念&#xff1a;聊聊我对AI Agents技术的一些看法。水平所限&#xff0c;我们也只是浅浅为大家梳理了一下 AI Agents 的概念发展与其代表性技术&#xff0c;一来不深入二来不细致&#xff0c;只能供大…

【Web-Note】 JavaScript概述

JavaSript基本语法 JavaSript程序不能独立运行&#xff0c;必须依赖于HTML文件。 <script type "text/javascript" [src "外部文件"]> JS语句块; </script> script标记是成对标记。 type属性&#xff1a;说明脚本的类型。 "text/jav…

机器学习库:numpy

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 文章目录 写在开头 基本数据格式 array 数据定位 argmax 数据生成 random.rand random.randn random.randint 维度拓展 expand_dim 结语 写在…

python桌面开发PyQt6库和工具库QTDesigner安装和配置

一、安装PyQt6 二、安装pyqt6-tools 三、安装外部工具 四、创建QTDesigner 1.首先查找designer.exe的路径&#xff08;可以自己在窗口中选择&#xff0c;也可以使用Everything搜索&#xff09; 2.使用Everything搜索后会出现多个designer.exe,选中&#xff0c;OpenPath 3.选择…

单片机学习7——定时器/计数器编程

#include<reg52.h>unsigned char a, num; sbit LED1 P1^0;void main() {num0;EA1;ET01;//IT00;//设置TMOD的工作模式TMOD0x01;//给定时器装初值&#xff0c;50000,50ms中断20次&#xff0c;就得到1sTH0(65536-50000)/256;TL0(65536-50000)%256;TR01; // 定时器/计数器启…

【Spring Boot】如何集成Swagger

Swagger简单介绍 Swagger是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化RESTful风格的Web服务。功能主要包含以下几点&#xff1a; 可以使前后端分离开发更加方便&#xff0c;有利于团队协作接口文档可以在线自动生成&#xff0c;有利于降低后端开发人员编写…

二叉树经典面试题—折纸

与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 1 折纸问题1.1 解决思路1.2 实现代码 &#x1f48e;总结 1 折纸问题 1.1 解决思路 请把一段纸条竖着放在桌子上&#xff0c;然后从纸条的下边向上方对折1次&#xff0c;压出折痕后展开。此时折痕是凹下去的&#xf…

包你学会十大内排序算法,广度深度兼备,动图演示,详细源码

内排序算法合集 文章目录 内排序算法合集前言冒泡排序冒泡排序的实现冒泡排序的简单实现冒泡排序的优化版本冒泡排序的复杂度分析 简单选择排序简单选择排序的实现简单选择排序的复杂度分析 直接插入排序直接插入排序的实现直接插入排序的复杂度分析 希尔排序希尔排序原理希尔排…

059-第三代软件开发-巧用工控板LED指示灯引脚

第三代软件开发-巧用工控板LED指示灯引脚 文章目录 第三代软件开发-巧用工控板LED指示灯引脚项目介绍巧用工控板LED指示灯引脚第一种方式第二种方式 总结 关键字&#xff1a; Qt、 Qml、 Power、 继电器、 IO 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项…

Leetcode—28.找出字符串中第一个匹配项的下标【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—28.找出字符串中第一个匹配项的下标 实现代码 int strStr(char* haystack, char* needle) {int len1 strlen(haystack);int len2 strlen(needle);int idx -1;int i 0;while(i < len1 - len2) {if(strncmp(haystac…

Django之admin页面样式定制(Simpleui)

好久不见&#xff0c;各位it朋友们&#xff01; 本篇文章我将向各位介绍Django框架中admin后台页面样式定制的一个插件库&#xff0c;名为Simpleui。 一&#xff09;简介 SimpleUI是一款简单易用的用户界面&#xff08;UI&#xff09;库&#xff0c;旨在帮助开发人员快速构建…

数据库基础教程之创建触发器,实现自动更新时间戳(四)

postgresql 更新时间戳需要通过触发器来实现,这里给出两种方法来实现。 创建字段create_time和edit_time 通过Navicat在添加字段时候将字段设置为timestamp类型,生成时间戳方式为CURRENT_TIMESTAMP或者设置为now(),然后点击保存。 新建函数 点击函数,然后在弹出的函数向导中…

ubuntu22.04中ros2 安装rosbridge

ros2 启动rosbridge&#xff1a; 要启动ROS2中的rosbridge&#xff0c;需要先安装ROS2的rosbridge_suite软件包。使用以下命令安装&#xff1a; 更新过可忽略 sudo apt-get update安装命令 sudo apt-get install ros--rosbridge-suite 注意&#xff1a; 将替换为正在使用的R…

rtsp点播异常出现‘circluar_buffer_size‘ option was set but it is xx

先说现象: 我使用potplay播放器来点播rtsp码流的时候可以点播成功&#xff0c;同事使用vlc和FFplay来点播rtsp码流的时候异常。 排查思路: 1.开始怀疑是oss账号问题&#xff0c;因为ts切片数据是保存在oss中的&#xff0c;我使用的是自己的oss账号&#xff0c;同事使用的是公司…

常见的1/2/3位数码管接线详解

今天玩数码管的时候接触到了数码管的接线&#xff0c;分享一下供刚开始接触的童鞋参考 首先了解什么是数码管 数码管是一种可以显示数字和其他信息的电子设备&#xff0c;是显示屏其中一类&#xff0c; 通过对其不同的管脚输入相对的电流&#xff0c;会使其发亮&#xff0c;从而…

哲学家就餐问题

文章目录&#xff1a; 问题描述及分析一次错误的尝试解决方案一解决方案二 问题描述及分析 哲学家就餐问题规定了有5位哲学家正在进行思考和就餐两种活动。用餐在一个桌子上进行&#xff0c;桌子上面有5个盘子和5个叉子&#xff0c;按照循环的方式分配。 问题的约束条件&#…

【数据中台】开源项目(2)-Wormhole流式处理平台

Wormhole 是一个一站式流式处理云平台解决方案&#xff08;SPaaS - Stream Processing as a Service&#xff09;。 Wormhole 面向大数据流式处理项目的开发管理运维人员&#xff0c;致力于提供统一抽象的概念体系&#xff0c;直观可视化的操作界面&#xff0c;简单流畅的配置管…

【React】Memo

组件重新渲染时缓存计算的结果。 实例&#xff1a;count1计算斐波那契数列&#xff0c;count2和count1可以触发数值变化。使用memo可以使只有在count1变化时触发斐波那契数列计算函数&#xff0c;而count2变化时不触发斐波那契数列计算函数。 import { useMemo } from "r…

二十六、搜索结果处理(排序、分页、高亮)

目录 一、排序 二、分页 1、深度分页问题 2、三种方案的优缺点 &#xff08;1&#xff09;fromsize 优点&#xff1a; 缺点&#xff1a; 场景&#xff1a; &#xff08;2&#xff09;after search 优点&#xff1a; 缺点&#xff1a; 场景&#xff1a; &#xff0…