当前位置: 首页 > news >正文

Linux【模拟实现C语言文件流】

✨个人主页: 北 海
🎉所属专栏: Linux学习之旅
🎃操作环境: CentOS 7.6 阿里云远程服务器

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、FILE 结构设计
    • 2、函数使用及分析
    • 3、文件打开 fopen
    • 4、文件关闭 fclose
    • 5、缓冲区刷新 fflush
    • 6、数据写入 fwrite
    • 7、数据读取 fread
    • 8、实际效果
    • 9、小结
    • 10、源码
  • 🌆总结


🌇前言

C语言 的文件流中,存在一个 FILE 结构体类型,其中包含了文件的诸多读写信息以及重要的文件描述符 fd,在此类型之上,诞生了 C语言 文件相关操作,如 fopenfclosefwrite 等,这些函数本质上都是对系统调用的封装,因此我们可以根据系统调用和缓冲区相关知识,模拟实现出一个简单的 C语言 文件流

图解

本文重点模拟实现 FILEC语言 文件操作相关函数

注意: 本文实现的只是一个简单的 demo,重点在于理解系统调用及缓冲区


🏙️正文

1、FILE 结构设计

在设计 FILE 结构体前,首先要清楚 FILE 中有自己的缓冲区及冲刷方式

图解
图片来源:《Linux基础IO》 - 2021dragon

缓冲区的大小和刷新方式因平台而异,这里我们将 大小设置为 1024 刷新方式选择 行缓冲,为了方便对缓冲区进行控制,还需要一个下标 _current,当然还有 最重要的文件描述符 _fd

#define BUFFER_SIZE 1024   //缓冲区大小//通过位图的方式,控制刷新方式
#define BUFFER_NONE 0x1    //无缓冲
#define BUFFER_LINE 0x2    //行缓冲
#define BUFFER_ALL 0x4     //全缓冲typedef struct MY_FILE
{char _buffer[BUFFER_SIZE];  //缓冲区size_t _current;    //缓冲区下标int _flush;  //刷新方式,位图结构int _fd;    //文件描述符
}MY_FILE;

当前模拟实现的 FILE 只具备最基本的功能,重点在于呈现原理

在模拟实现 C语言 文件操作相关函数前,需要先来简单回顾下


2、函数使用及分析

主要实现的函数有以下几个:

  • fopen 打开文件
  • fclose 关闭文件
  • fflush 进行缓冲区刷新
  • fwrite 对文件中写入数据
  • fread 读取文件数据
#include <stdio.h>
#include <assert.h>
#include <string.h>int main()
{//打开文件,写入数据FILE* fp = fopen("file.txt", "w");assert(fp);const char* str = "露易斯湖三面环山,层峦叠嶂,翠绿静谧的湖泊在宏伟山峰及壮观的维多利亚冰川的映照下更加秀丽迷人";char buff[1024] = { 0 };snprintf(buff, sizeof(buff), str);fwrite(buff, 1, sizeof(buff), fp);fclose(fp);return 0;
}

结果

#include <stdio.h>
#include <assert.h>
#include <string.h>int main()
{//打开文件,并从文件中读取信息FILE* fp = fopen("file.txt", "r+");assert(fp);char buff[1024] = { 0 };int n = fread(buff, 1, sizeof(buff) - 1, fp);buff[n] = '\0';printf("%s", buff);fclose(fp);return 0;
}

结果

fopen

  • 打开指定文件,可以以多种方式打开,若是以读方式打开时,文件不存在会报错

fclose

  • 根据 FILE* 关闭指定文件,不能重复关闭

fwrite

  • 对文件中写入指定数据,一般是借助缓冲区进行写入

fread

  • 读取文件数据,同理一般是借助缓冲区先进行读取

不同的缓冲区有不同的刷新策略,如果未触发相应的刷新策略,会导致数据滞留在缓冲区中,比如如果内存中的数据还没有刷新就断电的话,会导致数据丢失;除了通过特定方式进行缓冲区冲刷外,还可以手动刷新缓冲区,在 C语言 中,手动刷新缓冲区的函数为 fflush

#include <stdio.h>
#include <unistd.h>int main()
{int cnt = 20;while(cnt){printf("he");   //故意不触发缓冲cnt--;if(cnt % 10 == 5) {fflush(stdout); //刷新缓冲区printf("\n当前已冲刷,cnt: %d\n", cnt);}sleep(1);}return 0;
}

结果

总的来说,这些文件操作相关函数,都是在对缓冲区进行写入及冲刷,将数据拷贝给内核缓冲区,再由内核缓冲区刷给文件


3、文件打开 fopen

MY_FILE *my_fopen(const char *path, const char *mode); //打开文件

打开文件分为以下几步:

  • 根据传入的 mode 确认打开方式
  • 通过系统接口 open 打开文件
  • 创建 MY_FILE 结构体,初始化内容
  • 返回创建好的 MY_FILE 类型

因为打开文件存在多种失败情况:权限不对 / open 失败 / malloc 失败等,所以当打开文件失败后,需要返回 NULL

注意: 假设是因 malloc 失败的,那么在返回之前需要先关闭 fd,否则会造成资源浪费

// 打开文件
MY_FILE *my_fopen(const char *path, const char *mode)
{assert(path && mode);// 确定打开方式int flags = 0; // 打开方式// 读:O_RDONLY   读+:O_RDONLY | O_WRONLY// 写:O_WRONLY | O_CREAT | O_TRUNC   写+:O_WRONLY | O_CREAT | O_TRUNC | O_RDONLY// 追加: O_WRONLY | O_CREAT | O_APPEND   追加+:O_WRONLY | O_CREAT | O_APPEND | O_RDONLY// 注意:不考虑 b 二进制读写的情况if (*mode == 'r'){flags |= O_RDONLY;if (strcmp("r+", mode) == 0)flags |= O_WRONLY;}else if (*mode == 'w' || *mode == 'a'){flags |= (O_WRONLY | O_CREAT);if (*mode == 'w')flags |= O_TRUNC;elseflags |= O_APPEND;if (strcmp("w+", mode) == 0 || strcmp("a+", mode) == 0)flags |= O_RDONLY;}else{// 无效打开方式assert(false);}// 根据打开方式,打开文件// 注意新建文件需要设置权限int fd = 0;if (flags & O_CREAT)fd = open(path, flags, 0666);elsefd = open(path, flags);if (fd == -1){// 打开失败的情况return NULL;}// 打开成功了,创建 MY_FILE 结构体,并返回MY_FILE *new_file = (MY_FILE *)malloc(sizeof(MY_FILE));if (new_file == NULL){// 此处不能断言,需要返回空close(fd); // 需要先把 fd 关闭perror("malloc FILE fail!");return NULL;}// 初始化 MY_FILEmemset(new_file->_buffer, '\0', BUFFER_SIZE); // 初始化缓冲区new_file->_current = 0;                       // 下标置0new_file->_flush = BUFFER_LINE;               // 行刷新new_file->_fd = fd;                           // 设置文件描述符return new_file;
}

4、文件关闭 fclose

int my_fclose(MY_FILE *fp);   //关闭文件

文件在关闭前,需要先将缓冲区中的内容进行冲刷,否则会造成数据丢失

注意: my_fclose 返回值与 close 一致,因此可以复用

// 关闭文件
int my_fclose(MY_FILE *fp)
{assert(fp);// 刷新残余数据if (fp->_current > 0)my_fflush(fp);// 关闭 fdint ret = close(fp->_fd);// 释放已开辟的空间free(fp);fp = NULL;return ret;
}

5、缓冲区刷新 fflush

int my_fflush(MY_FILE *stream);   //缓冲区刷新

缓冲区冲刷是一个十分重要的动作,它决定着 IO 是否正确,这里的 my_fflush 是将用户级缓冲区中的数据冲刷至内核级缓冲区

冲刷的本质:拷贝,用户先将数据拷贝给用户层面的缓冲区,再系统调用将用户级缓冲区拷贝给内核级缓冲区,最后才将数据由内核级缓冲区拷贝给文件

因此 IO 是非常影响效率的。数据传输过程必须遵循冯诺依曼体系结构

函数 fsync

  • 将内核中的数据手动拷贝给目标文件(内核级缓冲区的刷新策略极为复杂,为了确保数据能正常传输,可以选择手动刷新)

注意: 在冲刷完用户级缓冲区后(write),需要将缓冲区清空,否则缓冲区就一直满载了

// 缓冲区刷新
int my_fflush(MY_FILE *stream)
{assert(stream);// 将数据写给文件int ret = write(stream->_fd, stream->_buffer, stream->_current);stream->_current = 0; // 每次刷新后,都需要清空缓冲区fsync(stream->_fd);   // 将内核中的数据强制刷给磁盘(文件)if (ret != -1) return 0;else return -1;
}

6、数据写入 fwrite

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream);    //数据写入

数据写入用户级缓冲区的步骤:

  1. 判断当前用户级缓冲区是否满载,如果满了,需要先刷新,再进行后续操作
  2. 获取当前待写入的数据大小 user_size 及用户级缓冲区剩余大小 my_size,方便进行后续操作
  3. 如果 my_size >= user_size,说明缓冲区容量足够,直接进行拷贝;否则说明缓冲区容量不足,需要重复冲刷->拷贝->再冲刷 的过程,直到将数据全部拷贝
  4. 拷贝完成后,需要判断是否触发相应的刷新策略,比如 行刷新->最后一个字符是否为 \n,如果满足条件就刷新缓冲区
  5. 数据写入完成,返回实际写入的字节数(简化版,即 user_size

如果是一次写不完的情况,需要通过循环写入数据,并且在缓冲区满后进行刷新,因为循环写入时,目标数据的读取位置是在不断变化的(一次读取一部分,不断后移),所以需要对读取位置和读取大小进行特殊处理

// 数据写入
size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
{// 写入先判断缓冲区是否已满if (stream->_current == BUFFER_SIZE)my_fflush(stream);size_t user_size = size * nmemb;                 // 用户想写入的字节数size_t my_size = BUFFER_SIZE - stream->_current; // 缓冲区中剩余可用空间size_t writen = 0;                               // 成功写入数据的大小if (my_size >= user_size){// 直接可用全部写入memcpy(stream->_buffer + stream->_current, ptr, user_size);stream->_current += user_size;writen = user_size;}else{// 一次写不完,需要分批写入size_t tmp = user_size; // 用于定位 ptr 的读取位置while (user_size > my_size){// 一次写入 my_size 个数据。user_size 会减小memcpy(stream->_buffer + stream->_current, ptr + (tmp - user_size), my_size);stream->_current += my_size; // 切记实时更新下标my_fflush(stream);           // 写入后,刷新缓冲区user_size -= my_size;my_size = BUFFER_SIZE - stream->_current;}// 最后空间肯定足够,再把数据写入缓冲区中memcpy(stream->_buffer + stream->_current, ptr + (tmp - user_size), user_size);stream->_current += user_size;writen = tmp;}// 通过刷新方式,判断是否进行刷新if (stream->_flush & BUFFER_NONE){// 无缓冲,直接冲刷my_fflush(stream);}else if (stream->_flush & BUFFER_LINE){// 行缓冲,遇见 '\n' 才刷新if (stream->_buffer[stream->_current - 1] == '\n')my_fflush(stream);}else{// 全缓冲,满了才刷新if (stream->_current == BUFFER_SIZE)my_fflush(stream);}// 为了简化,这里返回用户实际写入的字节数,即 user_sizereturn writen;
}

7、数据读取 fread

在进行数据读取时,需要经历 文件->内核级缓冲区->用户级缓冲区->目标空间 的繁琐过程,并且还要考虑 用户级缓冲区是否能够一次读取完所有数据,若不能,则需要多次读取

注意:

  • 读取前,如果用户级缓冲区中有数据的话,需要先将数据刷新给文件,方便后续进行操作
  • 读取与写入不同,读取结束后,需要考虑 \0 的问题(在最后一个位置加),如果不加的话,会导致识别错误;系统(内核)不需要 \0,但C语言中的字符串结尾必须加 \0,现在是 系统->用户(C语言)
// 数据读取
size_t my_fread(void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
{// 数据读取前,需要先把缓冲区刷新if (stream->_current > 0)my_fflush(stream);size_t user_size = size * nmemb;size_t my_size = BUFFER_SIZE;// 先将数据读取到FILE缓冲区中,再赋给 ptrif (my_size >= user_size){// 此时缓冲区中足够存储用户需要的所有数据,只需要读取一次read(stream->_fd, stream->_buffer, my_size);memcpy(ptr, stream->_buffer, my_size);*((char *)ptr + my_size - 1) = '\0';}else{int ret = 1;size_t tmp = user_size;while (ret){// 一次读不完,需要多读取几次ret = read(stream->_fd, stream->_buffer, my_size);stream->_buffer[ret] = '\0';memcpy(ptr + (tmp - user_size), stream->_buffer, my_size);stream->_current = 0;user_size -= my_size;}}size_t readn = strlen(ptr);return readn;
}

8、实际效果

现在通过自己写的 myStdio 测试C语言文件流操作

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>int main()
{//打开文件,写入一段话FILE* fp = fopen("log.txt", "w+");assert(fp);char inPutBuff[512] = "2110年1月1日,距离地球能源完全枯竭还有3650天。为了解决地球能源危机,\n人类制造了赛尔机器人和宇宙能源探索飞船赛尔号,去寻找神秘精灵看守的无尽能源。";int n = fwrite(inPutBuff, 1, strlen(inPutBuff), fp);printf("本次成功写入 %d 字节的数据", n);fclose(fp);printf("\n==============================\n");//重新打开文件fp = fopen("log.txt", "r");assert(fp);char outPutBuff[512] = { '\0' };n = fread(outPutBuff, 1, sizeof(outPutBuff), fp);printf("本次成功读取 %d 字节的数据,具体内容为: \n%s\n", n, outPutBuff);fclose(fp);fp = NULL;return 0;
}

结果:

结果
下面是库函数的结果:

结果
可以看出结果是一样的


9、小结

用户在进行文件流操作时,实际要进行至少三次的拷贝:用户->用户级缓冲区->内核级缓冲区->文件,C语言 中众多文件流操作都是在完成 用户->用户级缓冲区 的这一次拷贝动作,其他语言也是如此,最终都是通过系统调用将数据冲刷到磁盘(文件)中

图解
此时上一篇文章中的最后一个例子为什么会打印两次 hello fprintf 就很好理解了:因为没有触发刷新条件(文件一般为全缓冲),所以数据滞留在用户层缓冲区中,fork 创建子进程后,子进程结束,刷新用户层缓冲区[子进程],此时会触发写时拷贝机制,父子进程的用户层缓冲区不再是同一个;父进程结束后,刷新用户层缓冲区[父进程],因此会看见打印两次的奇怪现象

结果

最后再简单提一下 printfscanf 的工作原理

无论是什么类型,最终都要转为字符型进行存储,程序中的各种类型只是为了更好的解决问题
printf

  • 根据格式读取数据,如整型、浮点型,并将其转为字符串
  • 定义缓冲区,然后将字符串写入缓冲区(stdout
  • 最后结合一定的刷新策略,将数据进行冲刷

scanf

  • 读取数据至缓冲区(stdin
  • 根据格式将字符串扫描分割,存入字符指针数组
  • 最后将字符串转为对应的类型,赋值给相应的变量

这也就解释了为什么要确保 输出/输入 格式与数据匹配,如果不匹配的话,会导致 读取/赋值 错误


10、源码

关于 myStdio 的源码可以点击下方链接进行获取

模拟实现C语言文件流

图


🌆总结

以上就是本次关于 Linux【模拟实现C语言文件流】的全部内容了,通过 系统调用+缓冲区,我们模拟实现了一个简单版的 myStdio 库,在模拟实现过程中势必会遇到很多问题,而这些问题都能帮助你更好的理解缓冲区的本质:提高 IO 效率


星辰大海

相关文章推荐

Linux基础IO【重定向及缓冲区理解】

Linux基础IO【文件理解与操作】

===============

Linux【模拟实现简易版bash】

Linux进程控制【进程程序替换】

Linux进程控制【创建、终止、等待】

相关文章:

Linux【模拟实现C语言文件流】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f307;前言&#x1f3d9;️正文1、FILE 结构设计2、函数使用及分析3、文件打开 fopen4、文件关闭 fclose5、缓…...

APK文件结构

文件结构 assets文件用来存放需要打包到Android 应用程序的静态资源文件&#xff0c;例如图片资源文件&#xff0c;JSON配置文件&#xff0c;渠道配置文件&#xff0c;二进制数据文件&#xff0c;HTML5离线资源文件等 与res/raw目录不同的数&#xff0c;assets目录支持任意深度…...

RabbitMQ死信队列延迟交换机

RabbitMQ死信队列&延迟交换机 1.什么是死信 死信&死信队列 死信队列的应用&#xff1a; 基于死信队列在队列消息已满的情况下&#xff0c;消息也不会丢失实现延迟消费的效果。比如&#xff1a;下订单时&#xff0c;有15分钟的付款时间 2. 实现死信队列 2.1 准备E…...

武忠祥老师每日一题||不定积分基础训练(六)

解法一&#xff1a; 求出 f ( x ) , 进而对 f ( x ) 进行积分。 求出f(x),进而对f(x)进行积分。 求出f(x),进而对f(x)进行积分。 令 ln ⁡ x t , 原式 f ( t ) ln ⁡ ( 1 e t ) e t 令\ln xt,原式f(t)\frac{\ln (1e^t)}{e^t} 令lnxt,原式f(t)etln(1et)​ 则 ∫ f ( x ) d…...

C语言结构体详解

结构体是C语言中的一种高级数据类型&#xff0c;它可以将不同的数据类型组合在一起&#xff0c;形成一个自定义的数据类型。结构体为程序员提供了一种组织数据的方式&#xff0c;它为程序开发带来了极大的灵活性和扩展性。 C语言中的结构体定义如下&#xff1a; struct 结构体…...

非盲去模糊简单介绍

文章目录 非盲去模糊简单介绍基于频域的方法1. Wiener滤波器2. 逆滤波器和半正定滤波器 基于空域的方法1. 均值滤波器2. 高斯滤波器3. 双边滤波器 基于偏微分的方法1. 非线性扩散滤波2. 全变分模型3. Laplacian正则化模型 振铃效应应用总结 非盲去模糊简单介绍 非盲去模糊是一…...

C语言动态内存管理与文件操作:打造高效通讯录

本篇博客会讲解如何使用C语言实现一个通讯录。实现通讯录的过程中&#xff0c;会大量用到C语言的知识点&#xff0c;包括但不限于&#xff1a;函数、自定义类型、指针、动态内存管理、文件操作&#xff0c;这些知识点在我的其他博客中都有讲解过&#xff0c;欢迎大家阅读&#…...

2001-2021年全国30省就业人数数据

2001-2021年全国30省就业人数数据/各省就业人数数据 1、时间&#xff1a;2001-2021年 2、范围&#xff1a;包括30个省市不含西藏 3、指标&#xff1a;就业人数 4、来源&#xff1a;各省NJ、社会统计NJ 5、缺失情况说明&#xff1a;无缺失 6、指标说明&#xff1a; 就业人…...

自然语言处理知识抽取(pkuseg、DDParser安装及使用)

一、分词简介 1.基本概念 分词是自然语言处理中的一个重要步骤&#xff0c;它可以帮助我们将文本分成一个个词语&#xff0c;以便更好地理解和分析文本。在计算机视觉、语音识别、机器翻译等领域&#xff0c;分词都扮演着重要的角色。 目前&#xff0c;常用的分词库包括 jie…...

Linux内核面试知识总结

Linux启动过程 1、主机加电自检&#xff0c;加载BIOS硬件信息 2、读取MBR引导文件 3、引导linux内核 4、启动第一个进程init&#xff08;进程号永远为1&#xff09; 5、进度相应的运行级别 6、运行终端&#xff0c;输入用户名和密码 linux系统缺省的运行级别 关机、单机…...

深度学习模型压缩与优化加速

1. 简介 深度学习&#xff08;Deep Learning&#xff09;因其计算复杂度或参数冗余&#xff0c;在一些场景和设备上限制了相应的模型部署&#xff0c;需要借助模型压缩、系统优化加速、异构计算等方法突破瓶颈&#xff0c;即分别在算法模型、计算图或算子优化以及硬件加速等层…...

Kali 更换源(超详细,附国内优质镜像源地址)

1.进入管理员下的控制台。 2. 输入密码后点击“授权”。 3.在控制台内输入下面的内容。 vim /etc/apt/sources.list 4.敲击回车后会进入下面的页面。 5.来到这个页面后的第一部是按键盘上的“i”键&#xff0c;左下角出现“插入”后说明操作正确。 6.使用“#”将原本的源给注释…...

Java版工程项目管理系统平台+java版企业工程系统源码+助力工程企业实现数字化管理

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示1…...

搜索引擎测试报告

文章目录 一、项目背景二、项目功能三、测试目的四、测试环境五、测试计划1、功能测试2、自动化测试 六、测试结果 一、项目背景 java官方文档是我们在学习java语言中不可或缺的权威资料。相比于各种网站的Java资料&#xff0c;官方文档无论是语言表达还是组织方式都要更加全面…...

4年的测试工程师,你遇到过自身瓶颈期吗?又是怎样度过的?

从毕业到现在已经快4年啦&#xff0c;一直软件测试行业混迹。我不是牛人&#xff0c;但是自我感觉还算是个合格的测试工程师&#xff0c;有必要写下自己将近4年来的经历&#xff0c;给自我以提示&#xff0c;给刚入行的朋友提供点参考。 貌似这一点适应的行业最广&#xff0c;…...

【Python零基础学习入门篇④】——第四节:Python的列表、元组、集合和字典

⬇️⬇️⬇️⬇️⬇️⬇️ ⭐⭐⭐Hello&#xff0c;大家好呀我是陈童学哦&#xff0c;一个普通大一在校生&#xff0c;请大家多多关照呀嘿嘿&#x1f601;&#x1f60a;&#x1f618; &#x1f31f;&#x1f31f;&#x1f31f;技术这条路固然很艰辛&#xff0c;但既已选择&…...

3.6 cache存储器

学习步骤&#xff1a; 我会采取以下几个步骤来学习Cache存储器&#xff1a; 确定学习目标&#xff1a;Cache存储器作为一种高速缓存存储器&#xff0c;通常用于提高计算机系统的运行效率。因此&#xff0c;我需要明确学习Cache存储器的目的&#xff0c;包括了解其原理、结构和…...

Ubuntu零基础安装

Ubuntu零基础安装 首先我们需要安装VM&#xff0c;再安装ubuntu。 1、安装VM 进入VM官网 VM官网地址 选择下载试用版 下载Windows版本 下载完成后&#xff0c;点击安装包进行安装 至此就安装完毕了。 桌面会出现VM的图标。 点击打开&#xff0c;弹出如下画面&#xff1a; …...

热门的常用 API 大全分享

天气/环境 空气质量查询&#xff1a; 查询国内3400个城市的整点观测&#xff0c;获取指定城市的整点观测空气质量。未来7天生活指数&#xff1a;支持国内3400个城市以及国际4万个城市的天气指数数据&#xff0c;包括晨练、洗车、穿衣&#xff08;12项&#xff0c;有详细说明&a…...

利用粒子群算法设计无线传感器网络中的最优安全路由模型(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 无线传感器网络&#xff08;WSN&#xff09;由数十个、数百个甚至数千个自主传感器组成。这些传感器以无线方式嵌入环境中&…...

2023年华东杯数学建模B 题 期货价格相关性问题-思路解析

题目背景&#xff1a; 许多金融标的都有其内在的关联&#xff0c;如何从量价数据找到这种关联是一个有趣的 问题。例如在万得的“煤焦钢矿”板块中&#xff0c;有螺纹钢、铁矿石、不锈钢、热轧卷板、 硅铁、焦煤、焦炭、锰硅、线材 9 个品种。这些品种有些是上下游关系&…...

SAP UI5 之Controls (控件) 笔记三

文章目录 官网 Walkthrough学习-Controls控件1.0.1 在index.html中使用class id 属性控制页面展示的属性1.0.2 我们在index.js文件中引入 text文本控制1.0.3打开浏览器查看结果 官网 Walkthrough学习-Controls控件 Controls控件 在前面展示在浏览器中的Hello World 是在Html …...

哈希表题目:设计地铁系统

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;设计地铁系统 出处&#xff1a;1396. 设计地铁系统 难度 6 级 题目描述 要求 一个地铁系统正在收集乘客在不同站之间的花费时间。他们在使用这些数…...

云时通OMS:为零售品牌商打造高效的全渠道订单管理!

传统的零售企业围绕“人、货、场” 三要素来展开营销&#xff0c;其目标是基于“场”将货销售给更多的人。随着数字技术的应用&#xff0c;新零售模式下的“场”除了传统的线下店铺外&#xff0c;还拓展了多元化的线上渠道&#xff0c;比如小程序、企业APP、第三方平台、电商直…...

有必要给孩子买台灯吗?分享四款高品质的护眼台灯

有必要使用护眼台灯&#xff0c;尤其是有近视现象的孩子们。 现在很多孩子小学就开始近视了&#xff0c;保护视力刻不容缓呀! 很多人不知道&#xff0c;其实劣质光线是最大的眼睛杀手 给孩子随便买便宜的台灯&#xff0c;看着一样能用&#xff0c;其实时间久了 对孩子眼睛的…...

模板方法模式

模板方法模式 模板方法模式定义:使用场景角色定义抽象模板: 为抽象模板&#xff0c;它的方法分为两类AbstractClass1. 基本方法: 也叫做基本操作&#xff0c;是由子类实现的方法&#xff0c;并且在模板方法被调用。2. 模板方法: 可以有一个或几个&#xff0c;一般是一个具体方法…...

基于Yolov5的NEU-DET钢材表面缺陷检测,优化组合新颖程度较高:CVPR2023 DCNV3和InceptionNeXt,涨点明显

1.钢铁缺陷数据集介绍 NEU-DET钢材表面缺陷共有六大类,分别为:crazing,inclusion,patches,pitted_surface,rolled-in_scale,scratches 每个类别分布为: 训练结果如下: 2.基于yolov5s的训练 map值: 2.1 Inception-MetaNeXtStage 对应博客:https://cv2023.blog.csdn.n…...

【HarmonyOS】自定义组件之ArkUI实现通用标题栏组件

【关键字】 标题栏、常用内置组件整合、ArkUI、自定义组件 1、写在前面 在上一篇文章中我们通过Java语言实现了一个通用的标题栏组件&#xff0c;有需要的可以看下&#xff0c;文章地址&#xff1a; 华为开发者论坛 现在很多朋友都已经转战ArkTS语言了&#xff0c;那么今天…...

C#开发的OpenRA游戏的加载地图流程

C#开发的OpenRA游戏的加载地图流程 OpenRA游戏里,地图是一个很关键的数据, 因为地图里包括了地面状态,地面上建筑物状态, 还有玩家在地图上的布局情况,以及各种活动限制的条件。 在OpenRA里,需要把地图目录:OpenRA\mods\cnc\maps 里所有的文件进行加载, 并且保存在缓…...

python ast 详解与用法

目录 基本概念节点类型ast.Assignast.Nameast.Constantast.Callast.Attribute 结点的遍历ast源码示例 结点的修改示例 参考链接 基本概念 在 python 中&#xff0c;我们可以通过自带的 ast 模块来对解析遍历语法树&#xff0c;通过ast.parse()可以将字符串代码解析为抽象语法树…...

吴中公司网站建设找哪家/各大免费推广网站

3.9 闲聊数据类型 字符串: 单引号 双引号 三引号 数值: 整数 浮点 布尔 复数类型3.9.1 整型&#xff08;整数&#xff09; Python3的整型和长整型无缝结合 不再区分 长度取决于内存 优点-利于大数计算3.9.2 浮点型&#xff08;小数&#xff09; 科学记数法 E记法 也可以是e 源码…...

网站IP限制怎么做/常见的网络推广方法

入门快速开始Bootstrap-select需要jQuery v1.9.1 &#xff0c;Bootstrap的dropdown.js组件和Bootstrap的CSS。如果您尚未在项目中使用Bootstrap&#xff0c;则可以在此处下载Bootstrap v3.3.7最低要求的预编译版本。如果在Bootstrap v4 中使用bootstrap-select&#xff0c;你还…...

政府门户网站群集约化建设方案/小红书seo排名

记录一下Python的命名风格规范&#xff0c;文件名&#xff0c;包&#xff0c;模块&#xff0c;类&#xff0c;函数&#xff0c;变量….等等的命名风格&#xff0c;以及Google的Python命名规范。Python命名规范文件名全小写,可使用下划线包应该是简短的、小写的名字。如果下划线…...

渭南网站建设wifi/哈尔滨网站推广

多进程应用大批量的数据是非常舒服的一件事情。 处理之前理解两个概念&#xff1a;孤儿进程和僵尸进程 孤儿进程&#xff1a; 是指父进程在fork出子进程后&#xff0c;自己先完了。这个问题很尴尬&#xff0c;因为子进程从此变得无依无靠、无家可归&#xff0c;变成了孤儿。用术…...

行业网站建设哪家好/百度竞价排名费用

这两年&#xff0c;线上办公逐渐常态化&#xff0c;相信大家对ftp这个概念也比较熟悉了。ftp&#xff0c;即文件传输协议&#xff0c;线上办公就是用ftp软件进行文件传输的。那ftp传输文件大小有限制吗,ftp文件传输工具有哪些我们一起来看看。 一、ftp传输文件大小有限制吗 f…...

企业信息年报系统/seo网站推广招聘

在国内7个城市启动建设后&#xff0c;中国人工智能技术受邀参与国外的城市治理。 1月29日&#xff0c;马来西亚数字经济发展机构&#xff08;MDEC&#xff09;和吉隆坡市政厅&#xff08;DBKL&#xff09;联合宣布引入阿里云ET城市大脑&#xff0c;人工智能将全面应用到马来西亚…...