【Linux】深入理解文件缓冲区

2023-10-29

问题引入

首先看一段代码:

#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;
}

运行代码,结果如下:

image-20230828114238102

如果此时将输出结果重定向一下:

image-20230828114139893

会发现printffwrite都打印了两次。

究其原因,就要谈到缓冲区和缓冲区刷新策略的概念了。


如何理解缓冲区

假设你在青岛,你要从网上买一件商品,商家所在地是北京。你不会跑去北京自提,商家也不会跑到青岛亲自送货,因为成本又高效率又低。实际做法是商家通过寄快递的方式把商品寄到你所提供的地址附近的菜鸟驿站。

我们把北京比作外设,把商品比作信息,把青岛比作内存,把你比作进程,所以现在的情景就变成了位于内存的进程要从外设中读取一段信息。那此时菜鸟驿站是个什么角色呢?答案是缓冲区。

我们都有一个共识,那就是访问外设的效率是很低的。

为了解决这个问题,内存中会单独开一块空间作为缓冲区,当你想往外设输出数据的时候,很多时候并不会来一条输出一条,而是等缓冲区中数据达到一定数量之后再刷新到外设。同样地,当从外设中读取数据的时候,很多时候也不会只读取你想要的那一条,而是前前后后多读取一部分数据,你下次要读取的数据很大可能就在这部分数据中。这样一来就减少了io的次数,效率也就提高了。

这里就有几个问题需要理解一下:

  1. 将数据刷新到缓冲区或将数据从缓冲区刷新出来的本质是什么?

    按照正常的理解,刷新应该是要把这部分东西挪到另一个地方去,但挪动数据属实有点复杂,一要删除二要拷贝,拷贝数据不可避免,但是删除可以简化。比如可以记录一下缓冲区的当前大小,需要刷新缓冲区的时候,先把缓冲区的数据拷贝出去,然后把缓冲区的大小 置零,此时就完成了惰性删除。如果要把数据刷新到缓冲区,只需要将数据拷贝到缓冲区,然后原数据都不需要动。所以将数据刷新到缓冲区或者从缓冲区中刷新出来的本质是拷贝。更进一步,再加上等待数据刷新的这部分时间,不就是 一次完整的io了么。所以我们还可以得出io的本质就是等待+拷贝。

  2. 缓冲区的本质是什么?

    缓冲区的本质就是一段内存。

  3. 缓冲区是谁提供的 ?

    像我们使用printffwrite这类C语言接口进行输入输出时,都是对文件进行输入输出,而文件在C语言中是以FILE的形式描述组织的,所以对文件输入输出,实际上是向FILE中提供的缓冲区进行拷贝。

  4. 缓冲区是谁申请的?

    缓冲区是用户申请的。缓冲区本质是一段内存,当我们创建FILE对象时其实就会创建一段缓冲区,本质就是用malloc去申请,只不过我们看不到这个过程。

我们可以看一下FILE中的相关字段:

/usr/include/libio.h有一句typedef struct _IO_FILE FILE

我们可以在/usr/include/stdio.h中看到struct _IO_FILE的相关代码,下面是与缓冲区有关的部分字段:

struct _IO_FILE {
    int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
    #define _IO_file_flags _flags
    //缓冲区相关
    /* The following pointers correspond to the C++ streambuf protocol. */
    /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
    char* _IO_read_ptr; /* Current read pointer */
    char* _IO_read_end; /* End of get area. */
    char* _IO_read_base; /* Start of putback+get area. */
    char* _IO_write_base; /* Start of put area. */
    char* _IO_write_ptr; /* Current put pointer. */
    char* _IO_write_end; /* End of put area. */
    char* _IO_buf_base; /* Start of reserve area. */
    char* _IO_buf_end; /* End of reserve area. */
    /* The following fields are used to support backing up and undo. */
    char *_IO_save_base; /* Pointer to start of non-current get area. */
    char *_IO_backup_base; /* Pointer to first valid character of backup area */
    char *_IO_save_end; /* Pointer to end of non-current get area. */
    struct _IO_marker *_markers;
    struct _IO_FILE *_chain;
    int _fileno; //封装的文件描述符
    #if 0
    int _blksize;
    #else
    int _flags2;
    #endif
    _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
    #define __HAVE_COLUMN /* temporary */
    /* 1+column number of pbase(); 0 is unknown. */
    unsigned short _cur_column;
    signed char _vtable_offset;
    char _shortbuf[1];
    /* char* _save_gptr; char* _save_egptr; */
    _IO_lock_t *_lock;
    #ifdef _IO_USE_OLD_IO_FILE
};

缓冲区刷新策略

现在有一个缓冲区,是来一条数据就刷新一条数据,还是等数据达到一定数量才刷新呢?这就跟缓冲区的刷新策略有关了。

在效率方面,上面两种刷新策略无疑是后者效率最高,对于相同数量的数据刷新次数最少,访问外设的次数更少,效率自然更高。那是不是就采用这种刷新策略呢?非也,这还要看对应的场景。

对于显示器这种比较特殊的外设,我们需要时刻获取屏幕上的信息,所以数据从缓冲区刷新到显示器上的频率是会更高的。此时采用的刷新策略就是行缓冲,遇到\n就刷新。这也叫做行缓冲。

当然还有更为激进的刷新策略,就是即使刷新,来一点数据就刷一点。这也叫做无缓冲。

还有一种常见的刷新策略就是全缓冲,等到缓冲区满了再刷新。

所以常见的刷新策略就三条:

  1. 行缓冲,遇到换行符就刷新。
  2. 无缓冲,来一条数据刷新一条数据。
  3. 全缓冲,缓冲区满了才刷新。

当然还有特殊情况,比如进程退出时会把缓冲区刷新一遍,还有就是用户强制刷新,比如调用fflush


问题解释

现在应该能回答最初的问题了。

fprintffwrite都是向stdout中写入,在没有重定向的前提下就是向显示器输出,此时采取的刷新策略是行缓冲,所以会正常打印两条信息。

write是系统调用接口,不存在C语言提供的用户级缓冲区,所以就直接向文件写入了,打印出一条信息。

而一旦进行了重定向,此时输出目标是普通文件,普通文件采取的刷新策略是全缓冲。fprintffwritestdout的缓冲区写入的数据不足以填满缓冲区,所以在两条语句都执行结束之后缓冲区并没有及时刷新。而write由于没有这层缓冲区的缘故直接就写入了。此时创建一个子进程,因为是完全继承的父进程,自然而然地继承了父进程的缓冲区和缓冲区中的数据。下一步子进程和父进程都结束,在两个进程退出之前会先刷新缓冲区,所以两个进程的缓冲区中的数据都被刷新出来,也就是两组相同的字符串,每组两条。也就出现了最终的结果。


模拟一个文件缓冲区

知道了上面的原理,我们可以自己模拟实现一个建议的文件缓冲区及文件操作相关接口,封装一个简易的文件操作,加深对文件操作以及缓冲区的理解。

首先我们需要定义一个简易的FILE结构,因为是一个demo级别的,就封装几个核心字段:

#define SIZE 1024

#define SYNC_NOW    1 << 0  //无缓冲
#define SYNC_LINE   1 << 1  //行缓冲
#define SYNC_FULL   1 << 2  //全缓冲

typedef struct FILE_{
    int flags;        //刷新策略
    int fileno;       //文件描述符
    int cap;          //buffer的总容量
    int size;         //buffer当前的使用量
    char buffer[SIZE];//缓冲区,这里大小是写死的,实际缓冲区的大小是可变的
}FILE_;

此外我们实现四个基本接口,分别是fopen_fclose_fwrite_fflush_

仿照C语言库里函数的参数设计:

FILE_ *fopen_(const char *path_name, const char *mode); 
// path_name是要打开文件的路径
// mode是打开文件时的方式,是读、写还是追加

void fwrite_(const void *ptr, int num, FILE_ *fp);
// ptr是要写入的数据的地址
// num是要写入的数据的字节数
// fp是要向哪个文件中写入

void fclose_(FILE_ * fp);
// fp是要关闭的文件

void fflush_(FILE_ *fp);
// 立即刷新fp的缓冲区

对于fopen_函数,我们直接调用系统调用接口open,在调用之前只要根据mode设置好openflag参数即可,然后需要给文件创建相关的FILE结构对象并设置相关信息:

FILE_ *fopen_(const char *path_name, const char *mode)
{
    int flags = 0;
    int defaultMode=0666;

    if (strcmp(mode, "r") == 0)
    {
        // O_RDONLY -- 只读
        flags |= O_RDONLY;
    }
    else if (strcmp(mode, "w") == 0)
    {
        // O_WRONLY -- 只写,O_CREAT -- 文件不存在时创建文件,O_TRUNC -- 打开文件时先清空文件
        flags |= (O_WRONLY | O_CREAT | O_TRUNC);
    }
    else if (strcmp(mode, "a") == 0)
    {
        // O_APPEND -- 追加
        flags |= (O_WRONLY | O_CREAT | O_APPEND);
    }
    else
    {
        // ...
    }
    
    int fd = 0;

    // 如果是以只写的方式打开,则默认文件已经存在,直接打开即可
    if (flags & O_RDONLY) 
        fd = open(path_name, flags);
    // 不然则可能需要创建文件,一旦flags中有O_CREAT信息则需要传创建文件时的默认权限
    else 
        fd = open(path_name, flags, defaultMode);
    
    // 打开文件失败,设置错误信息并返回NULL,这也是为什么打开文件时会返回NULL的原因
    if (fd < 0)
    {
        const char *err = strerror(errno);
        write(2, err, strlen(err));
        return NULL;
    }
    
    // 打开文件成功,创建相关的的FILE结构对象
    FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp);

    // 设置FILE对象的字段信息
    fp->flags = SYNC_LINE; 		 // 默认设置成为行刷新
    fp->fileno = fd;	   		 // 设置文件描述符
    fp->cap = SIZE;		   		 // 设置缓冲区容量
    fp->size = 0;		  		 // 初始未进行写入
    memset(fp->buffer, 0 , SIZE);// 给缓冲区申请空间

    return fp; 	// 将fp返回
}

对于fclose_,我们也是直接调用系统调用接口close,只不过在关闭文件之前先刷新一下文件的缓冲区。

注意,C语言中的close函数只负责关闭文件,也就是断掉文件和文件描述符直接的连接关系。

FILE结构的生命周期由标准库管理。当程序终止时,所有仍然打开的文件都会被自动关闭,相应的资源和FILE结构会被释放。这也包括在main函数返回之前打开的文件。所以我们不需要显式地释放FILE结构。

void fclose_(FILE_ *fp)
{
    fflush_(fp);
    close(fp->fileno);
}

对于fwrite_,很简单,直接把要写入的数据拷贝到文件的缓冲区即可,此时缓冲区中可能已经有部分数据,所以写入的时候要注意细节。然后需要根据当前文件的刷新策略判断一下是否需要刷新。而数据从缓冲区刷新出去的本质就是调用write接口将数据从缓冲区中写入到文件对应的文件描述符中,然后将缓冲区的size置零进行惰性删除缓冲区内的数据:

void fwrite_(const void *ptr, int num, FILE_ *fp)
{
    // 写入到缓冲区中
    memcpy(fp->buffer + fp->size, ptr, num); //这里我们不考虑缓冲区溢出的问题
    fp->size += num;

    // 无缓冲,数据读进来之后立即刷新
    if (fp->flags & SYNC_NOW)
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; //清空缓冲区
    }
    
    // 全缓冲
    else if (fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->cap)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    
    // 行缓冲
    else if (fp->flags & SYNC_LINE)
    {
        // 这里只是为了简单地理解原理,不考虑诸如"abcd\nef"这类较为复杂的场景
        if(fp->buffer[fp->size-1] == '\n') 
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
}

最后就是fflush_,直接调用系统调用接口write将缓冲区的数据写到文件中,然后将缓冲区置空就好了。不过此时还有一个问题,我们用write将数据写入到文件,实际中间还有OS的内核缓冲区,可能不会直接刷新到硬盘中的文件,所以可以通过系统调用接口fsync强制要求OS将数据刷新到外设:

void fflush_(FILE_ *fp)
{
    if(fp->size > 0) 
        write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno); // 强制要求OS将数据刷新到外设
    fp->size = 0;      // 将缓冲区置空,无需对缓冲区原有的数据进行操作
}

最后我们整合一下,把所有相关头文件包含和声明放到头文件stdio_.h中:

#pragma once

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

#define SIZE 1024
#define SYNC_NOW    1 << 0  //无缓冲
#define SYNC_LINE   1 << 1  //行缓冲
#define SYNC_FULL   1 << 2  //全缓冲

typedef struct FILE_{
    int flags;        //刷新策略
    int fileno;       //文件描述符
    int cap;          //buffer的总容量
    int size;         //buffer当前的使用量
    char buffer[SIZE];//缓冲区,这里大小是写死的,实际缓冲区的大小是可变的
}FILE_;


FILE_ *fopen_(const char *path_name, const char *mode); 
// path_name是要打开文件的路径
// mode是打开文件时的方式,是读、写还是追加

void fwrite_(const void *ptr, int num, FILE_ *fp);
// ptr是要写入的数据的地址
// num是要写入的数据的字节数
// fp是要向哪个文件中写入

void fclose_(FILE_ * fp);
// fp是要关闭的文件

void fflush_(FILE_ *fp);
// 立即刷新fp的缓冲区

相关方法的实现整合在stdio_.c文件中:

#include "myStdio.h"


FILE_ *fopen_(const char *path_name, const char *mode)
{
    int flags = 0;
    int defaultMode=0666;

    if (strcmp(mode, "r") == 0)
    {
        // O_RDONLY -- 只读
        flags |= O_RDONLY;
    }
    else if (strcmp(mode, "w") == 0)
    {
        // O_WRONLY -- 只写,O_CREAT -- 文件不存在时创建文件,O_TRUNC -- 打开文件时先清空文件
        flags |= (O_WRONLY | O_CREAT | O_TRUNC);
    }
    else if (strcmp(mode, "a") == 0)
    {
        // O_APPEND -- 追加
        flags |= (O_WRONLY | O_CREAT | O_APPEND);
    }
    else
    {
        // ...
    }
    
    int fd = 0;

    // 如果是以只写的方式打开,则默认文件已经存在,直接打开即可
    if (flags & O_RDONLY) 
        fd = open(path_name, flags);
    // 不然则可能需要创建文件,一旦flags中有O_CREAT信息则需要传创建文件时的默认权限
    else 
        fd = open(path_name, flags, defaultMode);
    
    // 打开文件失败,设置错误信息并返回NULL,这也是为什么打开文件时会返回NULL的原因
    if (fd < 0)
    {
        const char *err = strerror(errno);
        write(2, err, strlen(err));
        return NULL;
    }
    
    // 打开文件成功,创建相关的的FILE结构对象
    FILE_ *fp = (FILE_*)malloc(sizeof(FILE_));
    assert(fp);

    // 设置FILE对象的字段信息
    fp->flags = SYNC_LINE; 		 // 默认设置成为行刷新
    fp->fileno = fd;	   		 // 设置文件描述符
    fp->cap = SIZE;		   		 // 设置缓冲区容量
    fp->size = 0;		  		 // 初始未进行写入
    memset(fp->buffer, 0 , SIZE);// 给缓冲区申请空间

    return fp; 	// 将fp返回
}


void fclose_(FILE_ *fp)
{
    fflush_(fp);
    close(fp->fileno);
}


void fwrite_(const void *ptr, int num, FILE_ *fp)
{
    // 写入到缓冲区中
    memcpy(fp->buffer + fp->size, ptr, num); //这里我们不考虑缓冲区溢出的问题
    fp->size += num;

    // 无缓冲,数据读进来之后立即刷新
    if (fp->flags & SYNC_NOW)
    {
        write(fp->fileno, fp->buffer, fp->size);
        fp->size = 0; //清空缓冲区
    }
    
    // 全缓冲
    else if (fp->flags & SYNC_FULL)
    {
        if(fp->size == fp->cap)
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
    
    // 行缓冲
    else if (fp->flags & SYNC_LINE)
    {
        // 这里只是为了简单地理解原理,不考虑诸如"abcd\nef"这类较为复杂的场景
        if(fp->buffer[fp->size-1] == '\n') 
        {
            write(fp->fileno, fp->buffer, fp->size);
            fp->size = 0;
        }
    }
}


void fflush_(FILE_ *fp)
{
    if(fp->size > 0) 
        write(fp->fileno, fp->buffer, fp->size);
    fsync(fp->fileno); // 强制要求OS将数据刷新到外设
    fp->size = 0;      // 将缓冲区置空,无需对缓冲区原有的数据进行操作
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】深入理解文件缓冲区 的相关文章

  • -ffast-math 可以安全地用于典型项目吗?

    在回答我建议的问题时 ffast math 有评论指出这是危险的 我个人的感觉是 在科学计算之外 是可以的 我还假设严肃的金融应用程序使用定点而不是浮点 当然 如果你想在你的项目中使用它 最终的答案是在你的项目上测试它 看看它有多大影响 但
  • 全局变量不好

    好吧 读完这篇文章和一些示例后 我仍然不清楚全局变量的含义 那么你的类中的私有变量是全局的吗 http www c2 com cgi wiki GlobalVariablesAreBad http www c2 com cgi wiki G
  • StackExchange Redis 删除所有以以下开头的键

    我有一个格式的密钥 Error 1 Error 24 Error 32 Using StackExchange Redis 我该怎么办KeyDelete在与格式匹配的所有键上Error 在另一个答案中我看到了 LUA 脚本 EVAL ret
  • 地图类容器的专用功能

    我想要专门为矢量和地图之类的容器设计一个函数模板 对于向量 我可以像下面那样做 但我不知道如何才能有一个专门版本的函数 该函数仅用于像地图这样的容器 include
  • MVVM:来自 FileOpenPicker 的图像绑定源

    我将 OnActivated 添加到 app xaml cs 中 它可以正常工作 protected async override void OnActivated IActivatedEventArgs args var continua
  • 处理器关联组 C#

    我使用的是 72 核的 Windows Server 2016 我看到有两组处理器 我的 net 应用程序将使用一个或其他组 我需要能够强制我的应用程序使用我选择的组 我看到下面的代码示例 但我无法使其工作 我可能传递了错误的变量 我希望应
  • 二叉树和快速排序?

    我有一个家庭作业 内容如下 别生气 担心 我是not请你帮我做作业 编写一个程序 通过使用二分查找的快速排序方法对一组数字进行排序 树 推荐的实现是使用递归算法 这是什么意思 到目前为止 这是我的解释 正如我在下面解释的那样 我认为两者都有
  • 如何在建立上下文时设置连接超时-PrincipalContext

    using PrincipalContext ctx new PrincipalContext ContextType Domain Domain UserName Password UserPrincipal U new UserPrin
  • 我想找到 C# 代码中所有后面没有括号的 if 语句。通过正则表达式

    我想找到所有if声明和for后面没有大括号的语句 当你在一个文件中写入一行时if声明您大多不会将其括在大括号中 所以我想找到所有这些if and for声明 请帮忙 就像我想捕捉这个声明 if childNode Name B return
  • 将 JavaScript 引擎嵌入到 .NET 中 [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 只是想知道是否有人尝试过将任何 js 引擎嵌入并实际集成到 net 环境中 我可以找到并实际使用 经过L
  • 向客户端发送状态码 500 时页面未呈现

    我有一个页面 通用处理程序 我想在该页面上向客户端返回状态代码 500 以指示出现问题 我这样做 Response StatusCode 500 Response StatusDescription Internal Server Erro
  • 如何找到进程启动时使用的原始用户名?

    有一个 perl 脚本需要以 root 身份运行 但我们必须确保运行该脚本的用户最初没有以用户 foo 身份登录 因为它将在脚本运行期间被删除 那么 我如何查明自登录以来可能已多次起诉的用户是否在该链中的任何时间都没有模拟过 foo 我发现
  • 括号内声明的对象的范围

    如果我声明一个这样的对象 void main myclass objectA anotherclass true true 0 即 我通过直接调用后者的构造函数来创建一个 objectA 和另一个对象 anotherclass anothe
  • 以编程方式阻止 Vista 桌面搜索 (WORDS) 对映射网络驱动器上的 pst 文件建立索引

    经过几天的多次尝试 我没有找到任何 100 的解决方案来解决这个问题 我的搜寻和调查范围 直接访问注册表 HKLM SOFTWARE Microsoft Windows Search CrawlScopeManager Windows Sy
  • C 中的 2 个字符要短

    我有2个字符 Char 128和查尔2 如何将这些字符转为 Short640 in C 我试过了 unsigned short getShort unsigned char array int offset short returnVal
  • 使用属性和性能

    我正在优化我的代码 我注意到使用属性 甚至自动属性 对执行时间有深远的影响 请参阅下面的示例 Test public void GetterVsField PropertyTest propertyTest new PropertyTest
  • 对 Action 方法的两个并行 ajax 请求排队,为什么?

    我正在使用 ASP NET MVC 开发一个视频网站 我希望在我的应用程序中拥有的一项功能是转码视频 但由于转码过程可能非常耗时 我想向客户端用户展示该过程的进度 因此 我的架构是使用一个控制器操作来处理整个转码过程 并将其进度写入存储在服
  • 在for循环中声明和初始化变量

    可以简单写一下吗 for int i 0 代替 int i for i 0 在 C 或 C 中 并且会变量i只能在循环内部访问 它在 C 中有效 它在 C 的原始版本中是不合法的 但在 C99 中被采用为 C 的一部分 当时一些 C 功能被
  • 将二进制长字符串转换为十六进制 C#

    我正在寻找一种将长二进制字符串转换为十六进制字符串的方法 二进制字符串看起来像这样 0110011010010111001001110101011100110100001101101000011001010110001101101011 我
  • 获取线段上最接近另一个点的点[关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 我想找到线段AB上最接近另一个点P的点 我的想法是 Get a1 and b1由直线公式y1 a1x b1 使用 A 点

随机推荐

  • build打包后怎么查看源码 vue_Vue-cli打包后怎么本地查看的操作

    Vue cli打包成dist后默认是必须在http服务器环境下才能正常运行 可以在本地启动一个http server服务查看 操作步骤如下 全局安装http server npm install g http server 进入dist根目
  • 数据结构与算法之二叉树: Leetcode 98. 验证二叉搜索树 (Typescript版)

    验证二叉搜索树 https leetcode cn problems validate binary search tree 描述 给你一个二叉树的根节点 root 判断其是否是一个有效的二叉搜索树 有效 二叉搜索树定义如下 节点的左子树只
  • 自定义数组类

    在学习c 的过程中 我们经常使用到数组 那怎么去定义一个类去实现数组的功能呢 我们先列出一些经常对数组进行的一些操作 1 创建一个指定容量的数组 2 用已有的数组初始化另一个数组 3 用已有的数组给另一个数组赋值 4 给数组添加元素 给数组
  • Intellij Idea golang插件开发

    1 安装Intellij idea 的golang插件 2 建立目录 D SystemFile GoWorkspace 在系统里面配置GOPATH D SystemFile GoWorkspace 然后在GOPATH目录下面建立 src b
  • 2019-6-18 车牌识别尝试-图像抗扭处理和SVM学习(opencv)

    抗扭曲函数deskew 利用opencv中svm算法学习图片和识别图片 抽取特征向量函数hot分析 车牌识别中涉及字符的识别 识别方法可以用opencv自带的机器学习算法svm 支持向量机 来实现 参见https docs opencv o
  • pandas的定义以及pandas的Series的初步使用(一)

    一 什么是pandas pandas是一种Python数据分析的利器 是一个开源的数据分析包 最初是应用于金融数据分析工具而开发出来的 因此pandas为时间序列分析提供了很好的支持 pandas是PyData项目的一部分 官网 http
  • 大数据实训kaggle比赛-房价预测(下)

    接着开始表演 下面介绍模型 Sequential 序贯模型 它是函数式模型的简略版 为最简单的线性 从头到尾的结构顺序 不分叉 是多个网络层的线性堆叠 实现方法 模型需要知道它所期待的输入的尺寸 出于这个原因 序贯模型中的第一层需要接收关于
  • 计算机视觉论文-2021-06-29

    本专栏是计算机视觉方向论文收集积累 时间 2021年6月29日 来源 paper digest 欢迎关注原创公众号 计算机视觉联盟 回复 西瓜书手推笔记 可获取我的机器学习纯手推笔记 直达笔记地址 机器学习手推笔记 GitHub地址 1 T
  • Python机器视觉--OpenCV进阶(核心)--图像直方图与掩膜直方图与直方图均衡化

    1 图像直方图 1 1 图像直方图的基本概念 在统计学中 直方图是一种对数据分布情况的图形表示 是一种二维统计图表 图像直方图是用一表示数字图像中亮度分布的直方图 标绘了图像中每个亮度值的像素数 可以借助观察该直方图了解需要如何调整亮度分布
  • Python教你一招,爬取链家二手房并做数据可视化分析

    前言 数据采集的步骤是固定 发送请求 模拟浏览器对于url地址发送请求 获取数据 获取网页数据内容 gt 请求那个链接地址 返回服务器响应数据 解析数据 提取我们需要的数据内容 保存数据 保存本地文件 所需模块 win R 输入cmd 输入
  • mybatis 配置打印sql

    1 在mybatis config xml中配置加一个setting 2 如果是spring集成mybatis的话 在sqlSessionFactory配置好configLocation属性 3 在日志文件中配置如下 DEBUG
  • 强化对信息安全事件的预防

    声明 本文是学习信息技术 安全技术 信息安全事件管理 第1部分 事件管理原理 而整理的学习笔记 分享出来希望更多人受益 如果存在侵权请及时联系我们 范围 本部分提出了信息安全事件管理的基本概念和过程阶段 并将这些概念与结构化方法的原理相结合
  • spring常用注解

    1 Controller 在SpringMVC 中 控制器Controller 负责处理由DispatcherServlet 分发的请求 它把用户请求的数据经过业务处理层处理之后封装成一个Model 然后再把该Model 返回给对应的Vie
  • mysql-5.6.16-win32免安装配置方法

    转载自 http blog csdn net fzhmoive article details 20042437 http blog csdn net leili0806 article details 8573636 1 下载MySQL
  • 口袋进化服务器维护,《口袋进化》新手指引.新手指导

    第一只精灵 你做好成为一名训练家的准备了么 作为一枚纯纯的萌新 在刚接触时 建议根据游戏提供的引导 来逐步熟悉基本玩法和养成体系 除此之外 别忘了阅读新手FAQ 在这里 为大家总结了一些新手常见的问题 方便大家享受游戏乐趣 Q 怎么升级 A
  • 编译protobuf静态库依赖顺序问题

    在项目中要加入protobuf协议支持 在编译成功生成程序包动态库后 发现启动运行报错 未定义的protobuf符号xxx等等 用ldd查看so文件 发现确实提示了未找到 大致的makefile文件如下 CXX g CFLAGS g Wal
  • 前后端分离命名规范:JSON下划线,对象小驼峰,以及Jackson常用配置

    在application yml中添加配置即可 spring jackson配置 jackson json和对象的命名转换 property naming strategy SNAKE CASE date format yyyy MM dd
  • QML下如何实现邻近区域无遮挡文字滚动

    在一些嵌入式显示设备中需要实现文字滚动功能 而利用QML是实现文字的滚动大家经常使用的功能 也非常简单 本文主要讲解一下如何避免2个邻近区域的文字滚动重叠问题 大家有没有按照如下的方式来设置文本的滚动 Window visible true
  • 机器学习流程是什么?简述机器学习流程!

    1 抽象成数学问题 明确问题是进行机器学习的第一步 机器学习的训练过程通常都是一件非常耗时的事情 胡乱尝试时间成本是非常高的 这里的抽象成数学问题 指的明确我们可以获得什么样的数据 抽象出的问题 是一个分类还是回归或者是聚类的问题 2 获取
  • 【Linux】深入理解文件缓冲区

    文章目录 问题引入 如何理解缓冲区 缓冲区刷新策略 问题解释 模拟一个文件缓冲区 问题引入 首先看一段代码 include