Linux——IO

news2024/11/27 9:44:21

✅<1>主页::我的代码爱吃辣
📃<2>知识讲解:Linux——文件系统
☂️<3>开发环境:Centos7
💬<4>前言:是不是只有C/C++有文件操作呢?python,java,php,go ..... 他们都是有文件操作?他们的文件操作一样吗?他们都有文件操作,且根据语言的语法不同,文件操作也是不同的。有没有一种同意的视角,看待所有语言的文件操作呢?

目录

一.回顾C文件IO相关操作

1.C语言文件写入

2.C语言文件读取

 3.输出信息到显示器有哪些方法

二.系统文件IO

1.open

 2.write

3.close

 4.read

三.对比C库与系统调用

四.如何管理文件

1.操作系统如何管理文件 

2.进程如何管理文件 ——文件描述符

3.文件描述符的分配规则

 三.重定向

1.重定向原理

2.dup2 系统调用

 四.理解FILE


一.回顾C文件IO相关操作

1.C语言文件写入

测试代码:

#include <stdio.h>
#include <string.h>
int main()
{
    FILE *fp = fopen("myfile", "w");
    if (!fp)
    {
        printf("fopen error!\n");
    }
    const char *msg = "hello Linux!\n";
    const char *msg2 = "hello C++!\n";
    int count = 5;
    while (count--)
    {
        // 向文件中写入,
        // 参数1:写入的数据C++
        // 参数2:写入的字符个数
        // 参数3:写入的数据元素的个数
        // 参数4:写入的文件结构体指针
        fwrite(msg, strlen(msg), 1, fp);
    }
    int n = 5;
    while (n--)
    {
        // 向文件中写入,
        // 参数1:写入的文件结构体指针
        // 参数2:格式化写入
        fprintf(fp, "[%d]:%s", n, msg2);
    }
    fclose(fp);
    return 0;
}

测试结果:

2.C语言文件读取

#include <stdio.h>
#include <string.h>
int main()
{
    FILE *fp = fopen("myfile", "r");
    if (!fp)
    {
        printf("fopen error!\n");
    }
    char buf[1024];
    const char *msg = "hello bit!\n";
    while (1)
    {
        // 注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
        size_t s = fread(buf, 1, strlen(msg), fp);
        if (s > 0)
        {
            buf[s] = 0;
            printf("%s", buf);
        }
        if (feof(fp))
        {
            break;
        }
    }
    fclose(fp);
    return 0;
}

 3.输出信息到显示器有哪些方法

#include <stdio.h>
#include <string.h>
int main()
{
    const char *msg = "hello fwrite\n";
    // 1.fwrite
    fwrite(msg, strlen(msg), 1, stdout);
    // 2.printf
    printf("hello printf\n");
    // 3.fprintf
    fprintf(stdout, "hello fprintf\n");
    return 0;
}

C库常见IO接口:

    // 1.默认向显示器格式化打印
    int printf(const char *format, ...);
    // 2.向指定的文件中格式化输入
    int fprintf(FILE * stream, const char *format, ...);
    // 3.向指定的空间中格式化输入
    int sprintf(char *str, const char *format, ...);
    // 4.向指定的空间中格式化输入指定个数字符
    int snprintf(char *str, size_t size, const char *format, ...);

 总结:

  1. C默认会打开三个输入输出流,分别是stdin, stdout, stderr
  2. 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

二.系统文件IO

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

1.open

隆重介绍一个系统调用:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,,就是一种位图结构,flags参数:

  1. O_RDONLY: 只读打开
  2. O_WRONLY: 只写打开
  3. O_RDWR : 读,写打开
  4. 这三个常量,必须指定一个且只能指定一个
  5. O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  6. O_APPEND: 追加写

 返回值:

  • 成功:新打开的文件描述符
  • 失败:-1

 2.write

       #include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

参数介绍:

  1. fd:要写入的文件描述符。
  2. buf:要写入的字符串。
  3. count:写入的个数。

3.close

       #include <unistd.h>

       int close(int fd);

 关闭指定的文件描述符的文件。

测试代码:

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

int main()
{
    // fd:文件描述符
    // mufile:打开的文件名
    // O_WRONLY :写方式 | O_CREAT:没有该文件就创建 | O_APPEND : 追加写入
    int fd = open("myfile", O_WRONLY | O_CREAT | O_APPEND, 0666);
    if (fd == -1)
    {
        perror("open");
    }

    int count = 5;
    char *msge = "hello C++ and Linux\n";

    while (count--)
    {
        ssize_t n = write(fd, msge, strlen(msge));
        if (n == -1)
        {
            perror("write:");
        }
    }

    close(fd);

    return 0;
}

测试结果:

 4.read

       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

参数:

  1. fd:读取文件的文件描述符
  2. buf:存储读取出的数据的缓冲区
  3. count:最大读取个数

返回值:

  • 读取成功:返回读取的字节数。
  • 读取失败:返回-1.

 测试代码:

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

int main()
{
    // fd:文件描述符
    // mufile:打开的文件名
    // ORDONLY:独方式打开
    int fd = open("myfile", O_RDONLY);
    if (fd == -1)
    {
        perror("open");
    }

    char buff[1024];

    // fd:读取文件的文件描述符
    // buff:存储读取数据的缓冲区
    // 1024:最大读取字节数
    ssize_t n = read(fd, buff, 1024);
    if (n == -1)
    {
        perror("write:");
    }

    printf(buff);
    close(fd);

    return 0;
}

 测试结果:

三.对比C库与系统调用

我们真正理解语言层面的文件操作吗?其实我们并不理解,因为这不是语言问题,这是系统问题。

是不是只有C/C++有文件操作呢?python,java,php,go ..... 他们都是有文件操作?他们的文件操作一样吗?他们都有文件操作,且根据语言的语法不同,文件操作也是不同的。有没有一种同意的视角,看待所有语言的文件操作呢?

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数:

 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write 都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图:

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

只要语言层支持了文件操作,那么语言层对下必然封装了系统调用。

四.如何管理文件

1.操作系统如何管理文件 

文件=内容+属性。

当一个文件没有被操作时,文件一般会被放在磁盘上。

当我们对一个文件进程操作的时候,文件需要被放进内存,因为冯诺依曼体系的限定!

当我们对文件进程操作的时候,文件需要被load到内存,load的是属性还是内容?至少要有属性被load。

当我们对文件进程操作的时候,文件需要被提前放进内存,操作文件的又不是我们一个,所以OS内部移动同时存在大量被打开的文件。那么操作系统如何管理这些被打开的文件呢?创建对应的结构体进行抽象,和数据机构进行组织。

每一个被打开的文件,都要在OS内部对应文件对象的struct结构体,可以将所有的struct_file结构体用某种数据结构连接起来,在OS内部,对被打开的文件进行管理,就转换成对链表的增删查改。

2.进程如何管理文件 ——文件描述符

 文件可以分为两大类,磁盘文件(没有被打开),内存文件(被打开)。

文件被打开,是指文件被以进程为代表的用户让操作系统打开的。

所以之前的文件操作,都是进程与被打开文件之间的关系。在OS的角度,就是PCB与struct_file的关系。

那么进程是如何管理自己打开的文件的呢?

open返回值:

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

int main()
{
    // 打开一个文件
    int fd = open("testfile", O_WRONLY | O_CREAT, 0666);

    // 打印文件描述符
    printf("%d\n", fd);

    return 0;
}

通过对open函数的学习,我们知道了文件描述符就是一个小整数。

 这里为什么是3?我们多打开几个文件看看:

#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("testfile", O_WRONLY | O_CREAT, 0666);
    int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);
    int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);
    int fd3 = open("testfile3", O_WRONLY | O_CREAT, 0666);

    // 打印文件描述符
    printf("%d\n", fd);
    printf("%d\n", fd1);
    printf("%d\n", fd2);
    printf("%d\n", fd3);

    return 0;
}

我们发现打印出的是连续的整数。但是没有还是从3开始的,那么会不会有0,1,2呢?

0 & 1 & 2 :

  1. Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
  2. 0,1,2对应的物理设备一般是:键盘,显示器,显示器

 所以输入输出还可以采用如下方式:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
    char buf[1024];
    // 0:标准输入的文件描述符——键盘文件
    ssize_t s = read(0, buf, sizeof(buf));
    if (s > 0)
    {
        buf[s] = 0;
        // 写入1号文件描述符的文件中——显示器文件
        // 写入2号文件描述符的文件中——显示器文件
        write(1, buf, strlen(buf));
        write(2, buf, strlen(buf));
    }
    return 0;
}

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

3.文件描述符的分配规则

 测试代码:

#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("testfile", O_WRONLY | O_CREAT, 0666);
    close(2);
    int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);
    int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);

    // 打印文件描述符
    printf("%d\n", fd);
    printf("%d\n", fd1);
    printf("%d\n", fd2);

    return 0;
}

测试结果:

 说明:

  1. 当我们关闭0,2号文件描述符,0,2文件描述符空着,新打开的文件描述符不再从3开始。
  2. fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

 三.重定向

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(0);
    int fd = open("testfile", O_WRONLY | O_CREAT, 0666);
    // 如果关闭1号文件描述符
    close(1);
    int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);
    int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);

    // 打印文件描述符
    printf("%d\n", fd);
    printf("%d\n", fd1);
    printf("%d\n", fd2);

    return 0;
}

测试结果:

说明:

  1. 本应该输出到显示器的内容,却输出到了文件中。这种现象就叫做重定向。
  2. 常见的重定向有:>, >>, <,输出重定向,追加重定向,输入重定向。

重定向的本质:

说明:

原本输入到显示器的数据输入到了其他文件,仅仅通过更改struct file*fdarray[ ]对应下标的存储的指针。

2.dup2 系统调用

#include <unistd.h>

int dup2(int oldfd, int newfd)

说明:

  • oldfd:需要重定向的文件描述符。
  • newfd:被重定向的文件描述符。

 测试代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
    int fd = open("./log", O_CREAT | O_RDWR, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    close(1);
    // 将fd对应的文件,重定向到1号文件描述符
    dup2(fd, 1);
    for (;;)
    {
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if (read_size < 0)
        {
            perror("read");
            break;
        }
        printf("%s", buf);
        fflush(stdout);
    }
    return 0;
}

测试结果:

printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了./log的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

 四.理解FILE

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

测试代码:

#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("testfile", O_CREAT | O_WRONLY, 0666);
    int fd1 = open("testfile1", O_CREAT | O_WRONLY, 0666);

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

    return 0;
}

测试结果:

看一段代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    const char *msg0 = "hello printf\n";
    const char *msg2 = "hello write\n";

    printf("%s", msg0);
    write(1, msg2, strlen(msg2));

    fork();
    return 0;
}

运行结果:

看到这里一切正常,如果我们将输出到显示器的数据,重定向到其他文件中:

 我们发现 printf 输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

  •  一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,fork之后。
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲,而是直接写入文件。

 综上:

  1. printf fwrite 等库函数会自带缓冲区,而 write 系统调用没有带缓冲区。
  2. 另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
  3. 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

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

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

相关文章

Code Ocean :一个用于数据科学和科学研究的在线平台【源码+文章解析】

Code Ocean&#xff08;https://codeocean.com/&#xff09;是一个用于数据科学和科学研究的在线平台&#xff0c;旨在帮助研究人员更轻松地管理、共享和复制研究代码和数据。以下是Code Ocean的主要用途和功能&#xff1a; 代码和数据的管理&#xff1a;Code Ocean允许研究人…

辨析目录表、文件打开表、文件分配表、索引表、FCB、inode、fd等文件系统常见名词

文章目录 1 解释2 形象配图 以下内容仅供简单的辨析这些文件系统最基本的名词&#xff0c;如果需要更深入的了解&#xff0c;请查阅相关转移书籍&#xff0c;如《现代操作系统》、《操作系统概念》 、《操作系统精髓与设计原理》等书籍。 1 解释 2 形象配图 文件打开表 文件分…

工控机连接Profinet转Modbus RTU网关与水泵变频器Modbus通讯

Profinet转Modbus RTU网关是一个具有高性能的通信设备&#xff0c;它能够将工控机上的Profinet协议转换成水泵变频器可识别的Modbus RTU协议&#xff0c;实现二者之间的通信。通过这种方式&#xff0c;工控机可以直接控制水泵变频器的运行状态&#xff0c;改变其工作频率&#…

Windows10下的GTSAM因子图安装与使用

Windows10下的GTSAM因子图安装与使用 一、windows系统预安装1. windows 10安装gcc2.windows 10 安装 boost3.CMake 安装与查看4.CMake 配置boost 二、GTSAM安装与使用三、CMAKE 创建立 使用GTSAM的Visual Studio项目参考文献 一、windows系统预安装 1. windows 10安装gcc htt…

【深度学习框架格式转化】【CPU】Pytorch模型转ONNX模型格式流程详解【入门】

【深度学习框架格式转化】【GPU】Pytorch模型转ONNX模型格式流程详解【入门】 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习框架格式转化】【GPU】Pytorch模型转ONNX模型格式流程详解【入门】前言PyTorch模型环境搭建(CPU)安装onn…

LCP 50. 宝石补给(每日一题)

欢迎各位勇者来到力扣新手村&#xff0c;在开始试炼之前&#xff0c;请各位勇者先进行「宝石补给」。 每位勇者初始都拥有一些能量宝石&#xff0c; gem[i] 表示第 i 位勇者的宝石数量。现在这些勇者们进行了一系列的赠送&#xff0c;operations[j] [x, y] 表示在第 j 次的赠送…

解决VS Code安装远程服务器插件慢的问题

解决VS Code安装远程服务器插件慢的问题 最近想在服务器上做juypter notebook的代码运行&#xff0c;发现要给服务器安装Jupyter插件&#xff0c;但是安装速度奇慢无比&#xff08;因为服务器不连外网&#xff09;&#xff0c;一开始查看从VS Code插件市场下载插件的博客&…

网络编程day02(socket套接字)

今日任务&#xff1a; TCP\UDP服务端客户端通信 TCP&#xff1a;代码 服务端&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #in…

项目提交按钮没防抖,差点影响了验收

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 表妹一键制作自己的五星红旗国庆头像&#xff0c;超好看 前言 一个运行了多年的ToB的项目&#xff0c;由于数据量越来越大&#xff0c;业务越来越复杂&…

【HarmonyOS】【DevEco Studio】盘点DevEco Studio日志获取途径

【关键词】 DevEco Studio、日志获取 【问题背景】 在收到IDE工单的时候&#xff0c;很多时候开发者出现的问题都需要提供一些日志&#xff0c;然后根据日志分析&#xff0c;那么你知道IDE各种日志的获取方式么&#xff1f;往下看 【获取方法】 一、idea.log获取 IDE界面H…

滴滴一面:说说MySQL主从数据同步机制

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如滴滴、阿里、汽车之家、极兔、有赞、希音、百度、网易、滴滴的面试资格&#xff0c;遇到一几个很重要的主从同步面试题&#xff1a; 说说MySQL主从同步的流程说说MySQL主从同步…

添加一个仅管理员可见的页面

例如我新加一个页面 申请一个路由 《插播》 前端是如何知道我们是管理员的呢&#xff0c;ant-design框架会帮我们存到InitialState里&#xff0c;做为全局变量 在access.ts里我们获取到了用户是否为管理员 &#xff08;用户存在且为管理员&#xff09; 框架为我们打通了个路由…

【深度学习实验】前馈神经网络(二):使用PyTorch实现不同激活函数(logistic、tanh、relu、leaky_relu)

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 定义激活函数 logistic(z) tanh(z) relu(z) leaky_relu(z, gamma0.1) 2. 定义输入、权重、偏置 3. 计算净活性值 4. 绘制激活函数的图像 5. 应用激活函数并…

MySQL基础—从零开始学习MySQL

01.MySQL课程介绍_哔哩哔哩_bilibili 1、MySQL安装 以管理员身份运行cmd net start mysql80net stop mysql80 客户端连接 1). 方式一&#xff1a;使用MySQL提供的客户端命令行工具 2). 方式二&#xff1a;使用系统自带的命令行工具执行指令 mysql [-h 127.0.0.1] [-P 3…

mysql知识大全

MySQL知识大全&#xff08;2&#xff09; MySqL 基础为1—7&#xff08;增删改查基础语法&#xff09;&#xff0c;MySQL进阶知识为8—11&#xff08;约束、数据库设计、多表查询、事务&#xff09; 1、数据库相关概念 以前我们做系统&#xff0c;数据持久化的存储采用的是文件…

【二叉树】二叉树展开为链表-力扣 114 题

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

【云原生】k8s-----集群调度

目录 1.k8s的list-watch机制 1.1 list-watc机制简介 1.2 根据list-watch机制&#xff0c;pod的创建流程 2.scheduler的调度策略 2.1 scheduler的调度策略简介 2.2 Scheduler预选策略的算法 2.3 Scheduler优选策略的算法 3. k8s中的标签管理及nodeSelector和nodeName的 调…

win10 安装 Langchain-Chatchat 避坑指南(2023年9月18日v0.2.4版本,包含全部下载内容!)

网上教程都是基于外网或者翻墙的&#xff0c;而且细节极其不清晰&#xff0c;尤其是最关键的模型下载。 另外提一句&#xff0c;我的显卡是&#xff1a;3080Ti 16GB版本&#xff0c;运行之后&#xff0c;显存占用13-14GB 1、安装Anaconda&#xff08;这个就不啰嗦了&#xff0c…

【SpringMVC】JSON注解全局异常处理机制

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Spring MVC》。&#x1f3…

Nue JS 造全新的 Web 生态

Nue JS 是最近开源的 Web 前端项目&#xff0c;用于构建用户界面&#xff0c;体积非常小&#xff08;压缩后 2.3kb&#xff09;。Nue JS 支持服务器端渲染 (SSR)、反应式组件和 “同构” 组合 ("isomorphic" combinations)。 Vue.js、React.js 或 Svelte&#xff0c;…