【Linux】19.基础IO(1)

文章目录

  • 1. 基础IO
    • 1. 文件
    • 2. 回顾C文件接口
      • 2.1 hello.c写文件
      • 2.2 hello.c读文件
      • 2.3 接口介绍
    • 3. open函数返回值
    • 3.1 文件描述符fd
    • 3.2 文件描述符的分配规则
      • 3.2.1 代码1
      • 3.2.2 代码2
      • 3.2.3 重定向底层原理代码示例
      • 3.2.4 使用 dup2 系统调用
    • 3.3 缓冲区刷新问题
    • 3.4 FILE


1. 基础IO

1. 文件

共识原理:

  1. 文件=内容+属性(文件被打开,必须先被加载到内存)

  2. 文件分为打开的文件和没打开的文件

  3. 打开的文件是谁打开的?

    是进程打开的。

  4. 没打开的文件:我们最关注什么?在哪里存放呢?

    在磁盘存放。

    没有被存放的文件非常多,文件如何被分门别类的放置好?我们要快速的进行增删改查,快速找到文件。

文件被打开,必须先被加载到内存

进程:打开的文件 = 1:n

操作系统内部一定存在大量的被打开的文件。

操作系统要不要管理这些被打开的文件呢?

在内核中,一个被打开的文件都必须有自己的文件打开对象,包含文件的很多属性。struct XXX(文件属性:struct XXX *next);


2. 回顾C文件接口

2.1 hello.c写文件

#include <stdio.h>
#include <string.h> 
int main() {FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");} const char *msg = "hello bit!\n";int count = 5;while(count--){fwrite(msg, strlen(msg), 1, fp);} fclose(fp); return 0; 
}

2.2 hello.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手册关于该函数的说明ssize_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;
}

当前路径是进程的当前路径cwd,如果我更改了当前进程的cwd,就可以把文件新建到其他目录。

w:写入之前,都会对文件进行清空处理。

w/a:都是写入,w清空并从头写,a在文件结尾,追加写。

如果我们想要向显示器打印:

C语言程序默认在启动的时候,会打开3个标准输入输出流(文件)

stdin:键盘文件

stdout:显示器文件

stderr:显示器文件

文件实际上是在磁盘上的,磁盘是外部设备,访问磁盘文件实际上是访问硬件。

几乎所有的库只要访问硬件设备,必须要封装系统调用。


2.3 接口介绍

open

man 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。
参数:O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读,写打开这三个常量,必须指定一个且只能指定一个O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限O_APPEND: 追加写
返回值:成功:新打开的文件描述符失败:-1
mode: 设置文件的访问权限(仅在创建文件时使用)

实例代码:

// 只读方式打开文件
int fd = open("file.txt", O_RDONLY);// 创建新文件,可读写
int fd = open("newfile.txt", O_RDWR | O_CREAT, 0644);

3. open函数返回值

  • 在认识返回值之前,先来认识一下两个概念: 系统调用库函数。上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数。

  • 而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

3.1 文件描述符fd

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

0 & 1 & 2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

0,1,2对应的物理设备一般是:键盘,显示器,显示器

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main()
{char buf[1024];         // 定义一个字符数组,用于存储从标准输入读取的数据,缓冲区大小为1024字节ssize_t s = read(0, buf, sizeof(buf)); // 调用read函数从文件描述符0(标准输入)读取数据// 读取的数据存储到buf中,最多读取sizeof(buf)字节,返回值为实际读取的字节数,类型为ssize_t(带符号的size_t)// 如果发生错误,返回-1;如果到达文件末尾,返回0if(s > 0){ // 检查read函数是否成功读取到数据,如果s > 0,表示成功读取了s字节的数据buf[s] = 0; // 在读取到的数据末尾添加一个空字符('\0'),将其转换为C字符串,这确保了后续使用字符串处理函数时的安全性write(1, buf, strlen(buf)); // 调用write函数将buf中的数据写入文件描述符1(标准输出)// 写入的数据长度为strlen(buf),即不包含末尾的空字符,返回值为实际写入的字节数write(2, buf, strlen(buf)); // 调用write函数将buf中的数据写入文件描述符2(标准错误)// 同样写入strlen(buf)字节的数据,这意味着程序会将输入的数据同时输出到标准输出和标准错误}return 0; 
}

f3b3273a3736d1fdf7e8bfe3b923618e

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


3.2 文件描述符的分配规则

3.2.1 代码1

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

输出发现是 fd: 3


3.2.2 代码2

关闭0或者2,再看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{close(0);//close(2);int fd = open("myfile", O_RDONLY);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。


3.2.3 重定向底层原理代码示例

代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#define filename "log.txt" // 定义文件名常量int main(){close(1);  // 关闭标准输出文件描述符(1)// 以创建、只写、清空方式打开文件,权限设置为666int fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);if(fd < 0){  // 打开文件失败则报错perror("open");return 1;}const char *msg = "hello linux\n";  // 定义要写入的字符串int cnt = 5;  // 循环次数// 循环写入5次字符串到文件while(cnt){// 由于前面关闭了标准输出,此时文件描述符1会被重定向到新打开的文件write(1, msg, strlen(msg));  cnt--;}close(fd);  // 关闭文件描述符return 0;
}

运行结果:

ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson19$ ll
total 36
drwxrwxr-x  2 ydk_108 ydk_108  4096 Jan 22 16:41 ./
drwxrwxr-x 15 ydk_108 ydk_108  4096 Jan 22 16:39 ../
-rw-rw-r--  1 ydk_108 ydk_108     0 Jan 22 16:39 log.txt
-rw-rw-r--  1 ydk_108 ydk_108    64 Jan 22 16:40 makefile
-rwxrwxr-x  1 ydk_108 ydk_108 16872 Jan 22 16:41 mytest*
-rw-rw-r--  1 ydk_108 ydk_108   881 Jan 22 16:41 mytest.c
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson19$ ./mytest
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson19$ cat log.txt
hello linux
hello linux
hello linux
hello linux
hello linux
ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson19$ 

本来向显示器写入的文件,被写入到了文件里面,这个叫做输出重定向。

  1. 文件描述符基础知识
  • Linux系统中,每个进程启动时默认打开3个文件描述符:

    • 0:标准输入(stdin)

    • 1:标准输出(stdout)

    • 2:标准错误(stderr)

  1. 程序主要步骤

    close(1);  // 关闭标准输出
    
    • 首先关闭标准输出(文件描述符1)
    • 这会使文件描述符1空闲出来
    int fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
    
    • 打开文件时使用的标志说明:
      • O_CREAT:如果文件不存在则创建
      • O_WRONLY:以只写方式打开
      • O_TRUNC:如果文件存在则清空内容
      • 0666:设置文件权限(读写权限)
    • 由于前面关闭了文件描述符1,新打开的文件会使用最小可用的文件描述符,也就是1
  2. 写入操作

    write(1, msg, strlen(msg));
    
    • 虽然代码中写的是向文件描述符1写入数据
    • 但因为文件描述符1已经被重定向到了log.txt文件
    • 所以实际上是在向log.txt文件写入数据
    • 循环5次,每次写入"hello linux\n"
  3. 程序执行效果

    • 会在当前目录创建log.txt文件
    • 文件中会包含5"hello linux"
    • 如果之前存在同名文件,内容会被清空后重写

一开始没有添加close(1);

cd690612ed87baae28c8cf7799b3f39c

后来添加了close(1);后:

45cbb14cfd9babca789739ea2899c5ce

我们也可以和之前的结合起来理解:

a1c1e63da4d5a48fb263cafe24159678


3.2.4 使用 dup2 系统调用

函数原型:

int dup2(int oldfd, int newfd);

代码解释:

  1. 函数原型
int dup2(int oldfd, int newfd);
  1. 参数含义
  • oldfd:旧的文件描述符(源)
  • newfd:新的文件描述符(目标)
  1. 功能说明
  • newfd 指向 oldfd 所指向的文件
  • 如果 newfd 已经打开,会先关闭它
  • 操作完成后,oldfdnewfd 指向同一个文件
  1. 返回值
  • 成功:返回新的文件描述符(即newfd的值)
  • 失败:返回-1,并设置errno
  1. 使用示例

dup2(fd, 0);

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main() {// 准备一个包含输入数据的文件const char *input_file = "input.txt";int fd = open(input_file, O_RDONLY);if(fd < 0) {perror("open");return 1;}// 将标准输入重定向到文件dup2(fd, 0);close(fd);  // 关闭原文件描述符// 现在从标准输入读取实际上是从文件读取char buf[256];scanf("%s", buf);  // 会从input.txt读取内容// input.txt的内容如果是:hello world// 则buf中只会读取到"hello",因为遇到空格就停止了读取printf("读取到的内容: %s\n", buf);return 0;
}

dup2(fd, 1);

#include <unistd.h>
#include <fcntl.h>int main() {// 打开一个文件int fd = open("output.txt", O_WRONLY|O_CREAT, 0644);// 将标准输出(1)重定向到fd指向的文件dup2(fd, 1);// 现在printf的输出会写入到output.txtprintf("Hello World\n");close(fd);return 0;
}

dup2(fd, 2);

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main() {// 打开错误日志文件const char *error_file = "error.log";int fd = open(error_file, O_WRONLY|O_CREAT|O_TRUNC, 0666);if(fd < 0) {perror("open");return 1;}// 将标准错误重定向到文件dup2(fd, 2);close(fd);// 以下错误信息会写入到error.log文件中fprintf(stderr, "这是一个错误信息\n");perror("测试错误");return 0;
}

3.3 缓冲区刷新问题

在C语言的I/O系统中,有三种缓冲模式:

  1. 无缓冲(_IONBF)
  • 数据直接发送到目的地,不进行缓冲
  • 每次读写都直接与设备或文件交互
  • 典型例子:stderr(标准错误)
  • 适用场景:错误信息输出等需要立即显示的场合

示例:

#include <stdio.h>int main() {// 设置stdout为无缓冲setvbuf(stdout, NULL, _IONBF, 0);printf("这句话会立即输出");return 0;
}
  1. 行缓冲(_IOLBF)
  • 遇到换行符’\n’时才进行实际的I/O操作
  • 缓冲区满时也会进行I/O操作
  • 典型例子:stdout(标准输出,当连接到终端时)
  • 适用场景:终端输出等交互场合

示例:

#include <stdio.h>int main() {printf("这句话不会立即输出");  // 不会立即显示printf("这句话会立即输出\n");  // 因为有\n,会立即显示fflush(stdout);  // 强制刷新缓冲区return 0;
}
  1. 全缓冲(_IOFBF)
  • 缓冲区满时才进行实际的I/O操作
  • 典型例子:文件操作
  • 适用场景:文件读写等对实时性要求不高的场合

示例:

#include <stdio.h>int main() {// 打开文件设置为全缓冲FILE *fp = fopen("test.txt", "w");if(fp == NULL) return 1;// 设置缓冲区大小为1024字节char buf[1024];setvbuf(fp, buf, _IOFBF, sizeof(buf));fprintf(fp, "这些数据会先放在缓冲区");fflush(fp);  // 强制写入文件fclose(fp);return 0;
}

行缓冲的缓冲区不是系统级别的缓冲区,行缓冲的缓冲区通常是由C语言标准库提供的。

显示器的文件刷新方案是行刷新,所以在printf执行完遇到\n的时候将数据进行刷新。

用户刷新的本质,就是将数据通过1+write写入到内核中。

缓冲区刷新问题:

  1. 无缓冲:直接刷新
  2. 行缓冲:不刷新,直到遇到\n
  3. 全缓冲:缓冲区满了才刷新

进程退出的时候,缓冲区也会刷新。

为什么要有这个缓冲区呢?

  1. 解决用户的效率问题
  2. 配合格式化

3.4 FILE

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。

所以C库当中的FILE结构体内部,必定封装了fd。

#include <stdio.h>
#include <string.h>int main()
{const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}

运行出结果:

hello printf
hello fwrite
hello write

但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

我们发现 printffwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?

肯定和fork有关!

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

printffwriteC库函数,它们使用FILE结构体的缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。

fork()时会复制父进程的内存空间,包括FILE结构体的缓冲区

此时缓冲区中还存有printffwrite写入但未刷新的数据

而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

父子进程各自都继承了这份缓冲区数据

但是进程退出之后,会统一刷新,写入文件当中。

当进程结束时,都会刷新各自的缓冲区,所以printffwrite的内容出现两次

write 没有变化,说明没有所谓的缓冲。

为什么write只出现一次:

  • write是系统调用,在fork()之前就已经直接写入文件
  • 没有经过缓冲区,所以不存在缓冲区复制的问题
  • 父子进程退出时也不会产生重复写入

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

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

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

相关文章

客户案例:向导ERP与金蝶云星空集成方案

一、客户背景 该客户公司主要致力于黄金、铂金、金镶玉首饰的研发设计、生产加工、批发及直营加盟业务。公司总部占地面积目前已达6000多平方米&#xff0c;拥有标准生产厂房和现代化生产设施&#xff0c;拥有一支完善的企业管理团队和专业技工队伍。 该企业目前同时采用向导 E…

RabbitMQ 在实际应用时要注意的问题

1. 幂等性保障 1.1 幂等性介绍 幂等性是数学和计算机科学中某些运算的性质,它们可以被多次应⽤,⽽不会改变初始应⽤的结果. 应⽤程序的幂等性介绍 在应⽤程序中,幂等性就是指对⼀个系统进⾏重复调⽤(相同参数),不论请求多少次,这些请求对系统的影响都是相同的效果. ⽐如数据库…

Cesium特效——城市白模的科技动效的各种效果

最终效果图如下&#xff1a; 实现方法&#xff1a; 步骤一&#xff1a;使用cesiumlib生产白模&#xff0c;格式为3dtiles 注意事项&#xff1a;采用其他方式可能导致白模贴地&#xff0c;从而导致不能实现该效果&#xff0c;例如把步骤二的服务地址改为Cesium Sandcastle 里的…

4_高并发内存池项目_高并发池内存释放设计_ThreadCache/CentralCache/PageCache回收并释放内存

高并发池内存释放设计 对各缓存层释放内存的设计&#xff0c;不仅仅是从上一层回收内存&#xff0c;还包括对回收回来的内存怎样处理更有利于下一缓存层的回收&#xff0c;提高效率。 高并发内存池内存释放步骤&#xff1a; 线程对象释放内存 ↓↓↓↓↓ ThreadCache(1.回收线…

centos9编译安装opensips 二【进阶篇-定制目录+模块】推荐

环境&#xff1a;centos9 last opensips -V version: opensips 3.6.0-dev (x86_64/linux) flags: STATS: On, DISABLE_NAGLE, USE_MCAST, SHM_MMAP, PKG_MALLOC, Q_MALLOC, F_MALLOC, HP_MALLOC, DBG_MALLOC, CC_O0, FAST_LOCK-ADAPTIVE_WAIT ADAPTIVE_WAIT_LOOPS1024, MAX_RE…

分子动力学模拟里的术语:leap-frog蛙跳算法和‌Velocity-Verlet算法

分子动力学模拟&#xff08;Molecular Dynamics Simulation&#xff0c;简称MD&#xff09;是一种基于经典力学原理的计算物理方法&#xff0c;用于模拟原子和分子在给定时间内的运动和相互作用‌。以下是关于分子动力学模拟的一些核心术语和概念&#xff1a; ‌定义系统‌&am…

iOS开发设计模式篇第二篇MVVM设计模式

目录 一、什么是MVVM 二、MVVM 的主要特点 三、MVVM 的架构图 四、MVVM 与其他模式的对比 五、如何在iOS中实现MVVM 1.Model 2.ViewModel 3.View (ViewController) 4.双向绑定 5.文中完整的代码地址 六、MVVM 的优缺点 1.优点 2.缺点 七、MVVM 的应用场景 八、结…

【C++图论 并集查找】2492. 两个城市间路径的最小分数|1679

本文涉及知识点 C图论 并集查找&#xff08;并查集) LeetCode2492. 两个城市间路径的最小分数 给你一个正整数 n &#xff0c;表示总共有 n 个城市&#xff0c;城市从 1 到 n 编号。给你一个二维数组 roads &#xff0c;其中 roads[i] [ai, bi, distancei] 表示城市 ai 和 …

Linux应用编程(五)USB应用开发-libusb库

一、基础知识 1. USB接口是什么&#xff1f; USB接口&#xff08;Universal Serial Bus&#xff09;是一种通用串行总线&#xff0c;广泛使用的接口标准&#xff0c;主要用于连接计算机与外围设备&#xff08;如键盘、鼠标、打印机、存储设备等&#xff09;之间的数据传输和电…

⽤vector数组实现树的存储(孩⼦表示法)c++

在我们遇到的算法题中&#xff0c; ⼀般给出的树结构都是有编号的&#xff0c;这样会简化我们之后存储树的操作 &#xff0c;⼀般提供两个信息&#xff1b; 结点的个数 n;n-1条x结点与y结点相连的边 题⽬描述: ⼀共9个结点셈 1号结点为根节点&#xff0c;接下来8⾏&#xff…

一个基于Python+Appium的手机自动化项目~~

本项目通过PythonAppium实现了抖音手机店铺的自动化询价&#xff0c;可以直接输出excel&#xff0c;并带有详细的LOG输出。 1.excel输出效果: 2. LOG效果: 具体文件内容见GitCode&#xff1a; 项目首页 - douyingoods:一个基于Pythonappium的手机自动化项目&#xff0c;实现了…

基于微信小程序的童装商城的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

方便快捷的软件展示平台查找和下载所需的软件

## 软件展示平台项目概述 背景&#xff1a; 随着互联网的发展&#xff0c;软件的数量日益增长&#xff0c;用户需要一款方便快捷的软件展示平台来查找和下载所需的软件。本软件展示平台旨在为用户提供一个集中展示各类软件的平台&#xff0c;方便用户快速找到所需的软件并进行…

进程、线程和协程的区别

进程、线程和协程的区别 在操作系统中&#xff0c;进程、线程 和 协程 是并发编程中的核心概念。 1. 进程 定义 进程是程序的一次执行过程&#xff0c;是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的地址空间和系统资源。 特点 独立性&#xff1a;每个…

MinIO的安装与使用

目录 1、安装MinIO 1.1 下载 MinIO 可执行文件 1.2 检查 MinIO 是否安装成功 1.3 设置数据存储目录 1.4 配置环境变量&#xff08;可选&#xff09; 1.5 编写启动的脚本 1.6 开放端口 1.7 访问 2、项目实战 2.1 引入依赖 2.2 配置yml文件 2.3 编写Minio配置类 2.4…

CSDN 博客之星 2024:默语的技术进阶与社区耕耘之旅

CSDN 博客之星 2024&#xff1a;默语的技术进阶与社区耕耘之旅 &#x1f31f; 默语&#xff0c;是一位在技术分享与社区建设中坚持深耕的博客作者。今年&#xff0c;我有幸再次入围成为 CSDN 博客之星TOP300 的一员&#xff0c;这既是对过往努力的肯定&#xff0c;也是对未来探…

土壤墒情中土壤 pH 值的监测方法与意义

土壤&#xff0c;作为农作物生长的根基&#xff0c;其质量对农业生产有着深远影响。在衡量土壤质量的众多指标中&#xff0c;土壤 pH 值是极为关键的一项。它不仅反映了土壤的酸碱度&#xff0c;还直接或间接地影响着土壤中养分的有效性、微生物的活性以及农作物的生长发育。因…

Trimble三维激光扫描-地下公共设施维护的新途径【沪敖3D】

三维激光扫描技术生成了复杂隧道网络的高度详细的三维模型 项目背景 纽约州北部的地下通道网络已有100年历史&#xff0c;其中包含供暖系统、电线和其他公用设施&#xff0c;现在已经开始显露出老化迹象。由于安全原因&#xff0c;第三方的进入受到限制&#xff0c;在没有现成纸…

开发环境搭建-1:配置 WSL (类 centos 的 oracle linux 官方镜像)

一些 Linux 基本概念 个人理解&#xff0c;并且为了便于理解&#xff0c;可能会存在一些问题&#xff0c;如果有根本上的错误希望大家及时指出 发行版 WSL 的系统是基于特定发行版的特定版本的 Linux 发行版 有固定组织维护的、开箱就能用的 Linux 发行版由固定的团队、社…

从零到上线:Node.js 项目的完整部署流程(包含 Docker 和 CICD)

从零到上线&#xff1a;Node.js 项目的完整部署流程&#xff08;包含 Docker 和 CI/CD&#xff09; 目录 项目初始化&#xff1a;构建一个简单的 Node.js 应用设置 Docker 环境&#xff1a;容器化你的应用配置 CI/CD&#xff1a;自动化构建与部署上线前的最后检查&#xff1a;…