【Linux】第二十四站:模拟实现C语言文件标准库

一、实现细节

1.需要实现的函数

#include "mystdio.h"

int main()
{
    _FILE* fp = _fopen("test.txt","w");
    if(fp == NULL) return 1;
    const char* msg = "hell world";

    _fwrite(fp,msg,strlen(msg));

    _fclose(fp);
    return 0;
}

如上代码所示,就是我们需要实现的功能

也就是说,我们在头文件中需要写出以下的代码

#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__

#include <string.h>
typedef struct IO_FILE
{
    int fileno; 
}_FILE;
_FILE* _fopen(const char* filename, const char* flag);
int _fwrite(_FILE* fp, const char* s, int len);
void _fclose(_FILE* fp);

#endif

2.fopen的实现

它的实现逻辑是这样的,首先我们要根据对应的flag去选择以什么样的方式去打开某个文件。

当文件成功打开以后,我们需要创建这个文件的结构体对象,然后将文件描述符写入到文件结构体中。最后返回这个文件的结构体

_FILE* _fopen(const char* filename, const char* flag)
{
    assert(filename);
    assert(flag);
    int f = 0;
    int fd = -1;
    if(strcmp(flag, "w") == 0)
    {
        f = (O_WRONLY|O_CREAT|O_TRUNC);
        fd = open(filename, f, FILE_MODE);
    }
    else if(strcmp(flag, "a") == 0) 
    {
        f = (O_WRONLY|O_CREAT|O_APPEND);
        fd = open(filename, f, FILE_MODE);
    }
    else if(strcmp(flag, "r") == 0) 
    {
        f = O_RDONLY;
        fd = open(filename, f);
    }
    else
    {
        return NULL;
    }

    if(fd == -1)
    {
        return NULL;
    }
    _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
    if(fp == NULL)
    {
        return NULL;
    }
    fp->fileno = fd;
    return fp;
}

3.fclose

对于文件的关闭,我们只需要利用close这个系统调用接口,关闭对应的文件描述符即可,然后释放掉这个文件结构体

void _fclose(_FILE* fp)
{
    if(fp == NULL)
    {
        return;
    }
    close(fp->fileno);
    free(fp);
}

4.fwrite

int _fwrite(_FILE* fp, const char* s, int len)
{
    return write(fp->fileno,s,len);    
}

对于这个fwrite而言,它只需要利用write这个接口直接去调用这个写入的接口即可

5.测试

我们现在可以简单的去跑一下代码

image-20231130163432240

image-20231130163342862

如果我们将代码改为追加模式

image-20231130163456193

那么运行结果为

image-20231130163550599

所以这样我们就不用再去使用系统调用了,而是直接使用库函数即可。

这就是一层封装,在windows等其他操作系统也是一样,我们只需要根据其对应的系统调用写上对应的代码即可。这样就可以实现跨平台性了

像java中的jvm也是一样的,它本身就是用C/C++写的,然后java的代码就是跑在它的上面的

6.缓冲区的实现

在这里我们不去考虑一些异常处理,也不考虑一些局部的问题

image-20231130165707739

在C语言中,即存在输入缓冲区,也有输出缓冲区。

对于输入缓冲区,就是我们在输入的时候,我们的设备只认识字符,即便是123也会将其认为字符1字符2字符3。

image-20231130165832594

image-20231130165853368

即对于上面的这些接口,我们也可以看到,这些都是void类型,也就是说并不关心具体是什么类型。

具体这些数据是什么类型全部交由上层来解释。所以我们的键盘显示器都叫做字符输入输出设备。所以才可以格式化输入输出

总之全部当作字符来出来

我们现在只实现一个输出缓冲区

image-20231130175546868

flag代表着的是刷新的模式,有无刷新,行刷新,全刷新三种策略。我们分别用宏来表示

最终我们的代码改为如下

#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#define FILE_MODE 0666

//"w","a",r
_FILE* _fopen(const char* filename, const char* flag)
{
    assert(filename);
    assert(flag);
    int f = 0;
    int fd = -1;
    if(strcmp(flag, "w") == 0)
    {
        f = (O_WRONLY|O_CREAT|O_TRUNC);
        fd = open(filename, f, FILE_MODE);
    }
    else if(strcmp(flag, "a") == 0) 
   {
        f = (O_WRONLY|O_CREAT|O_APPEND);
        fd = open(filename, f, FILE_MODE);
    }
    else if(strcmp(flag, "r") == 0) 
    {
        f = O_RDONLY;
        fd = open(filename, f);
    }
    else
    {
        return NULL;
    }
    if(fd == -1)
    {
        return NULL;
    }
    _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
    if(fp == NULL)
    {
        return NULL;
    }
    fp->fileno = fd;
    fp->flag = FLUSH_LINE;
    fp->out_pos = 0;
    return fp;
}
int _fwrite(_FILE* fp, const char* s, int len)
{
    memcpy(&(fp->outbuffer[fp->out_pos]), s, len);
    fp->out_pos += len;
    if(fp->flag&FLUSH_NOW)
    {
        write(fp->fileno, fp->outbuffer, fp->out_pos);
        fp->out_pos = 0;
    }
    else if(fp->flag&FLUSH_LINE)
    {
        if(fp->outbuffer[fp->out_pos-1]=='\n')
        {
            write(fp->fileno, fp->outbuffer, fp->out_pos);
            fp->out_pos = 0;
        }
    }
    else if(fp->flag&FLUSH_ALL)
    {
        if(fp->out_pos == SIZE)
        {
            write(fp->fileno, fp->outbuffer, fp->out_pos);
            fp->out_pos = 0;
        }
    }
    return len;
}
void _fclose(_FILE* fp)
{
    if(fp == NULL)
    {
        return;
    }
    close(fp->fileno);
    free(fp);
}

对于main.c中,代码改为如下

#include "mystdio.h"
#include <unistd.h>
#define Filename "test.txt"

int main()
{
    _FILE* fp = _fopen(Filename,"w");
    if(fp == NULL) return 1;
    const char* msg = "hell world\n";
    int cnt = 10;
    while(cnt)
    {
        _fwrite(fp,msg,strlen(msg));
        sleep(1);
        cnt--;
    }
    _fclose(fp);
    return 0;
}

运行结果为

image-20231130175724942

以上是行刷新的策略

我们可以试一下全刷新

image-20231130175852601

其实我们发现结果不对,因为即便进程退出了也没有刷新出来

image-20231130180102790

这是因为我们缺少了在进程退出时强制刷新的部分,如下所示

image-20231130180752407

image-20231130180738897

7.FILE中缓冲区的意义

因为,如果我们写一次就往操作系统写一次,这样效率太低,不如暂一大批,然后再跟操作系统去交互。

即让C语言的接口调用起来变得更快。

二、完整代码

mystdio.h

#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__

#include <string.h>
#define SIZE 1024

#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_ALL 4

typedef struct IO_FILE
{
    int fileno; 
    //char inbuffer[SIZE];
    //int in_pos;
    char outbuffer[SIZE];
    int out_pos;
    int flag;
}_FILE;
_FILE* _fopen(const char* filename, const char* flag);
int _fwrite(_FILE* fp, const char* s, int len);
void _fclose(_FILE* fp);

#endif

mystdio.c

#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#define FILE_MODE 0666

//"w","a",r
_FILE* _fopen(const char* filename, const char* flag)
{
    assert(filename);
    assert(flag);
    int f = 0;
    int fd = -1;
    if(strcmp(flag, "w") == 0)
    {
        f = (O_WRONLY|O_CREAT|O_TRUNC);
        fd = open(filename, f, FILE_MODE);
    }
    else if(strcmp(flag, "a") == 0) 
   {
        f = (O_WRONLY|O_CREAT|O_APPEND);
        fd = open(filename, f, FILE_MODE);
    }
    else if(strcmp(flag, "r") == 0) 
    {
        f = O_RDONLY;
        fd = open(filename, f);
    }
    else
    {
        return NULL;
    }
    if(fd == -1)
    {
        return NULL;
    }
    _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
    if(fp == NULL)
    {
        return NULL;
    }
    fp->fileno = fd;
    //fp->flag = FLUSH_LINE;
    fp->flag = FLUSH_ALL;
    fp->out_pos = 0;
    return fp;
}
int _fwrite(_FILE* fp, const char* s, int len)
{
    memcpy(&(fp->outbuffer[fp->out_pos]), s, len);
    fp->out_pos += len;
    if(fp->flag&FLUSH_NOW)
    {
        write(fp->fileno, fp->outbuffer, fp->out_pos);
        fp->out_pos = 0;
    }
    else if(fp->flag&FLUSH_LINE)
    {
        if(fp->outbuffer[fp->out_pos-1]=='\n')
        {
            write(fp->fileno, fp->outbuffer, fp->out_pos);
            fp->out_pos = 0;
        }
    }
    else if(fp->flag&FLUSH_ALL)
    {
        if(fp->out_pos == SIZE)
        {
            write(fp->fileno, fp->outbuffer, fp->out_pos);
            fp->out_pos = 0;
        }
    }
    return len;
}
void _fflush(_FILE* fp)
{
    if(fp->out_pos > 0)
    {
        write(fp->fileno, fp->outbuffer, fp->out_pos);
        fp->out_pos = 0;
    }
} 
void _fclose(_FILE* fp)
{
    if(fp == NULL)
    {
        return;
    }
    _fflush(fp);
    close(fp->fileno);
    free(fp);
}

main.c

#include "mystdio.h"
#include <unistd.h>
#define Filename "test.txt"

int main()
{
    _FILE* fp = _fopen(Filename,"w");
    if(fp == NULL) return 1;
    const char* msg = "hell world\n";
    int cnt = 10;
    while(cnt)
    {
        _fwrite(fp,msg,strlen(msg));
        sleep(1);
        cnt--;
    }
    _fclose(fp);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青色_忘川

你的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值