实现自己的数据库二

news2025/1/12 11:58:38

一 前言

上次数据库支持了一个测试表的插入和查询,但是数据全部保存到磁盘中的,如果程序重启后,数据都会全部丢了,所以需要持久化到磁盘上,像sqlite一样,简单的将数据库的数据保存到一个磁盘文件上。

二 实现原理

93cc339481f96dce3f5791f6c25b891d.png
image.png

我们上次已经将数据库的数据持久化到一块4kB的内存块上,可以方便的将这块内存持久化到文件上即可,具体实现步骤:

  1. 我们定义一个抽象结构Pager,这是对文件结构和页面的综合抽象。具体如下:

typedef struct {
  int file_descriptor;
  uint32_t file_length;
 void* pages[TABLE_MAX_PAGES];
} Pager;

typedef struct {
      uint32_t num_rows;
      Pager *pager;
 } Table;
  1. 操作数据的时候通过接口获取到对应的page内容,如果page存在,则直接使用,如果page不存在,则从文件中加载对应位置的4KB的数据保存到对应page中。

  2. 关闭数据库的时候,再将整个结果回写到磁盘上。

  3. 关键实现是通过lseek来定位文件的位置,存入文件的数据内容按照行数进行定位的,如同再内存中一样。

  4. 关闭数据的时候也是通过定位文件后,按照内存块形式直接写入到磁盘,挺简单,也挺神奇。

三 实现代码

首先Table的申请改变了,由原来的完全从内存读取,改成除了所需的内存外,还和打开文件关联起来。

253 Pager *pager_open(const char *filename)
254 {
              // 打开文件,为可读可写,没有则创建
255         int fd = open(filename, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR);
256         if (fd == -1) {
257                 printf("Unable to open file.\n");
258                 exit(EXIT_FAILURE);
259         }
               // 定位到尾部 返回文件的大小
260         int  file_length = lseek(fd, 0, SEEK_END);
              // 申请Pager,且和文件关联
261         Pager *pager = (Pager*)malloc(sizeof(Pager));
262         pager->file_descriptor = fd;
263         pager->file_length = file_length;
264 
265         for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) {
266                 pager->pages[i] = NULL;
267         }
268         return pager;
269 }


304 Table *db_open(const char *filename)
305 {
306         Pager *pager      = pager_open(filename);
307         uint32_t num_rows = pager->file_length / ROW_SIZE;
308         Table *table      = (Table *)malloc(sizeof(Table));
309         table->pager = pager;
310         table->num_rows   = num_rows;
311         return table;
312 }

在数据库关闭的时候,将数据刷新到磁盘上,如下:

332 void db_close(Table *table)
333 {
334         Pager *pager = table->pager;
              // 根据行数计算页面数量
335         uint32_t num_full_pages = table->num_rows / ROWS_PER_PAGE;
336         for (uint32_t i = 0; i < num_full_pages; i++) {
337                 if (pager->pages[i] == NULL) {
338                         break;
339                 }
340                 pager_flush(pager, i, PAGE_SIZE);
341         }
              // 处理不够一页多余的行数
342         uint32_t num_add_rows = table->num_rows % ROWS_PER_PAGE;
343         if (num_add_rows > 0) {
344                 uint32_t page_num = num_full_pages;
345                 if (pager->pages[page_num] != NULL) {
346                         pager_flush(pager, page_num, num_add_rows * ROW_SIZE);
347                         free(pager->pages[page_num]);
348                         pager->pages[page_num] = NULL;
349                 }
350         }
351         int result = close(pager->file_descriptor);
352         if (result == -1) {
353                 printf("Error closing db file.\n");
354                 exit(EXIT_FAILURE);
355         }
356         for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) {
357                 Pager *page = (Pager *) pager->pages[i];
358                 if (page) {
359                         free(page);
360                         pager->pages[i] = NULL;
361                 }
362         }
363         free(pager);
364         free(table);
365 }

下面是比较核心的代码,就是将pager刷新到磁盘上,代码如下:

314 void pager_flush(Pager *pager, uint32_t page_num, uint32_t size)
315 {
316         if (pager->pages[page_num] == NULL) {
317                 printf("Tried to flush null page.\n");
318                 exit(EXIT_FAILURE);
319         }
               // 根据页面数定位到文件的具体位置
320         off_t offset = lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET);
321         if (offset == -1) {
322                 printf("Error seeking:%d\n", errno);
323                 exit(EXIT_FAILURE);
324         }
               // 将数据写入到文件,每次大部分写入一个Page大小,也可能写入部分
325         ssize_t byte_written = write(pager->file_descriptor, pager->pages[page_num], size);
326         if (byte_written == -1) {
327                 printf("Error writing:%d", errno);
328                 exit(EXIT_FAILURE);
329         }
330 }

写入磁盘没问题,那读取的时候,读取的时候和内存读的时候也类似,只是改成从文件中读取:

282 ExecuteResult execute_select(Statement *statement, Table *table)
283 {
284         Row row;
285         for (uint32_t i = 0; i < table->num_rows; i++) {
                       // 将数据反序列化然后打印出来
286                 deserialize_row(row_slot(table, i), &row);
287                 print_row(&row);
288         }
289         return EXECUTE_SUCCESS;
290 }

获取内存页面和页面的偏移量

244 void *row_slot(Table *table, uint32_t row_num)
245 {
              // 定位页数
246         uint32_t page_num = row_num / ROWS_PER_PAGE;
               // 获取页面
247         void *page = get_page(table->pager, page_num);
248         uint32_t row_offset  = row_num % ROWS_PER_PAGE;
               // 定位页内的行偏移量
249         uint32_t byte_offset = row_offset * ROW_SIZE;
250         return (char *)page + byte_offset;
251 }

下面是读取文件的具体页面:

100 void *get_page(Pager *pager, uint32_t page_num)
101 {
102         if (page_num > TABLE_MAX_PAGES) {
103                 printf("Tried to fetch page number out of bounds. %d>%d", page_num, TABLE_MAX_PAGES);
104                 exit(EXIT_FAILURE);
105         }
106         if (pager->pages[page_num] == NULL) {
107                 void  *page = malloc(PAGE_SIZE);
108                 uint32_t num_pages = pager->file_length / PAGE_SIZE;
109                 if (pager->file_length % PAGE_SIZE) {
110                         num_pages += 1;
111                 }
                      // 从文件中读取一个页面大小,如果原来文件为0则忽略
112                 if (page_num <= num_pages && pager->file_length != 0) {
113                         lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET);
114                         ssize_t bytes_read = read(pager->file_descriptor, page, PAGE_SIZE);
115                         if (bytes_read == -1) {
116                                 printf("Error reading file:%d\n", errno);
117                                 exit(EXIT_FAILURE);
118                         }
119                 }
120                 pager->pages[page_num] = page;
121         }
122        return pager->pages[page_num];
123 }

四 最终完整代码

#include <stdio.h>
#if defined(_MSC_VER)
        #include <BaseTsd.h>
        typedef SSIZE_T ssize_t;
#endif

#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <cstdlib>
#include <stdbool.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
// #include <file.h>



#define EXIT_SUCCESS 0
#define MAX_LEN 1024
#pragma warning(disable : 4819)

#define COLUMN_USERNAME_SIZE 32
#define COLUMN_EMAIL_SIZE 255
#define TABLE_MAX_PAGES 100

typedef enum {
        META_COMMAND_SUCCESS,
        META_COMMAND_UNRECOGNIZED_COMMAND
} MetaCommandResult;

typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT, PREPARE_NEGATIVE_ID, PREPARE_STRING_TOO_LONG, PREPARE_SYNTAX_ERROR} PrepareResult;
typedef enum { EXECUTE_SUCCESS, EXECUTE_TABLE_FULL } ExecuteResult;



typedef struct {
        uint32_t id;
        char username[COLUMN_USERNAME_SIZE + 1];
        char email[COLUMN_EMAIL_SIZE + 1];
} Row;

typedef struct {
        int file_descriptor;
        uint32_t file_length;
        void *pages[TABLE_MAX_PAGES];
} Pager;

typedef struct {
        uint32_t num_rows;
        Pager *pager;
} Table;



typedef struct {
        StatementType type;
        Row row_to_insert;
} Statement;



typedef struct {
        char *buffer;
        size_t buffer_length;
        ssize_t input_length;
} InputBuffer;


#define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute)

const uint32_t ID_SIZE = size_of_attribute(Row, id);
const uint32_t USERNAME_SIZE = size_of_attribute(Row, username);
const uint32_t EMAIL_SIZE = size_of_attribute(Row, email);
const uint32_t ID_OFFSET = 0;

const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE;
const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE;
const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE;


const uint32_t PAGE_SIZE = 4096;
const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE;
const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES;



void db_close(Table *table);

InputBuffer *new_input_buffer()
{
        InputBuffer *input_buffer = (InputBuffer *)malloc(sizeof(InputBuffer));
        input_buffer->buffer = NULL;
        input_buffer->buffer_length = 0;
        input_buffer->input_length = 0;
        return input_buffer;
}

void *get_page(Pager *pager, uint32_t page_num)
{
        if (page_num > TABLE_MAX_PAGES) {
                printf("Tried to fetch page number out of bounds. %d>%d", page_num, TABLE_MAX_PAGES);
                exit(EXIT_FAILURE);
        }
        if (pager->pages[page_num] == NULL) {
                void  *page = malloc(PAGE_SIZE);
                uint32_t num_pages = pager->file_length / PAGE_SIZE;
                if (pager->file_length % PAGE_SIZE) {
                        num_pages += 1;
                }
                if (page_num <= num_pages && pager->file_length != 0) {
                        lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET);
                        ssize_t bytes_read = read(pager->file_descriptor, page, PAGE_SIZE);
                        if (bytes_read == -1) {
                                printf("Error reading file:%d\n", errno);
                                exit(EXIT_FAILURE);
                        }
                }
                pager->pages[page_num] = page;
        }
       return pager->pages[page_num];
}

int  getline_my(char **buffer, size_t *length, FILE *fd)
{
        int i = 0;
        char ch;
        char buf[MAX_LEN] = {0};

        while ((ch = fgetc(fd)) != EOF && ch != '\n') {
                if (MAX_LEN - 1 == i) {
                        break;
                }
                buf[i++] = ch;
        }
        *length = i;
        buf[i] = '\0';
        *buffer = (char *)malloc(sizeof(char) * (i + 1));
        strncpy(*buffer, buf, i + 1);
        return i;
}

void print_row(Row *row)
{
        printf("(%d,%s,%s)\n", row->id, row->username, row->email);
}

void read_input(InputBuffer *input_buffer)
{
        ssize_t bytes_read =
            getline_my(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin);

        if (bytes_read <= 0) {
                printf("Error reading input\n");
                exit(EXIT_FAILURE);
        }

        input_buffer->input_length = bytes_read ;
        input_buffer->buffer[bytes_read] = 0;
}

void close_input_buffer(InputBuffer *input_buffer)
{
        free(input_buffer->buffer);
        free(input_buffer);
        input_buffer = NULL;
}


///

MetaCommandResult do_meta_command(InputBuffer *input_buffer, Table *table)
{
        if (strcmp(input_buffer->buffer, ".exit") == 0) {
                close_input_buffer(input_buffer);
                db_close(table);
                exit(EXIT_SUCCESS);
        } else {
                return META_COMMAND_UNRECOGNIZED_COMMAND;
        }
}


PrepareResult prepare_insert(InputBuffer *input_buffer, Statement *statement)
{
        statement->type = STATEMENT_INSERT;

        char *keyword = strtok(input_buffer->buffer, " ");
        char *id_string = strtok(NULL, " ");
        char *username = strtok(NULL, " ");
        char *email = strtok(NULL, " ");

        if (id_string == NULL || username == NULL || email == NULL) {
                return PREPARE_SYNTAX_ERROR;
        }

        int id = atoi(id_string);
        if (id < 0) {
                return PREPARE_NEGATIVE_ID;
        }
        if (strlen(username) > COLUMN_USERNAME_SIZE) {
                return PREPARE_STRING_TOO_LONG;
        }
        if (strlen(email) > COLUMN_EMAIL_SIZE) {
                return PREPARE_STRING_TOO_LONG;
        }

        statement->row_to_insert.id = id;
        strcpy(statement->row_to_insert.username, username);
        strcpy(statement->row_to_insert.email, email);

        return PREPARE_SUCCESS;
}

PrepareResult prepare_statement(InputBuffer *input_buffer,
                                Statement *statement)
{
        if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
                return prepare_insert(input_buffer, statement);
        } else if (strncmp(input_buffer->buffer, "select", 6) == 0) {
                statement->type = STATEMENT_SELECT;
                return PREPARE_SUCCESS;
        }

        return PREPARE_UNRECOGNIZED_STATEMENT;
}


void serialize_row(Row *source, void *destination)
{
        memcpy((char *)destination + ID_OFFSET, &(source->id), ID_SIZE);
        memcpy((char *)destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE);
        memcpy((char *)destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE);
}

void deserialize_row(void *source, Row *destination)
{
        memcpy(&(destination->id), (char *)source + ID_OFFSET, ID_SIZE);
        memcpy(&(destination->username), (char *)source + USERNAME_OFFSET, USERNAME_SIZE);
        memcpy(&(destination->email), (char *)source + EMAIL_OFFSET, EMAIL_SIZE);
}

void *row_slot(Table *table, uint32_t row_num)
{
        uint32_t page_num = row_num / ROWS_PER_PAGE;
        void *page = get_page(table->pager, page_num);
        uint32_t row_offset  = row_num % ROWS_PER_PAGE;
        uint32_t byte_offset = row_offset * ROW_SIZE;
        return (char *)page + byte_offset;
}

Pager *pager_open(const char *filename)
{
        int fd = open(filename, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR);
        if (fd == -1) {
                printf("Unable to open file.\n");
                exit(EXIT_FAILURE);
        }
        int  file_length = lseek(fd, 0, SEEK_END);
        Pager *pager = (Pager*)malloc(sizeof(Pager));
        pager->file_descriptor = fd;
        pager->file_length = file_length;

        for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) {
                pager->pages[i] = NULL;
        }
        return pager;
}

ExecuteResult execute_insert(Statement *statement, Table *table)
{
        if (table->num_rows >= TABLE_MAX_ROWS) {
                return EXECUTE_TABLE_FULL;
        }
        Row *row_to_insert = &(statement->row_to_insert);
        serialize_row(row_to_insert, row_slot(table, table->num_rows));
        table->num_rows += 1;
        return EXECUTE_SUCCESS;
}

ExecuteResult execute_select(Statement *statement, Table *table)
{
        Row row;
        for (uint32_t i = 0; i < table->num_rows; i++) {
                deserialize_row(row_slot(table, i), &row);
                print_row(&row);
        }
        return EXECUTE_SUCCESS;
}

ExecuteResult execute_statement(Statement *statement, Table *table)
{
        switch (statement->type) {
                case (STATEMENT_INSERT):
                        return execute_insert(statement, table);
                case (STATEMENT_SELECT):
                        return  execute_select(statement, table);
        }
}



Table *db_open(const char *filename)
{
        Pager *pager      = pager_open(filename);
        uint32_t num_rows = pager->file_length / ROW_SIZE;
        Table *table      = (Table *)malloc(sizeof(Table));
        table->pager = pager;
        table->num_rows   = num_rows;
        return table;
}

void pager_flush(Pager *pager, uint32_t page_num, uint32_t size)
{
        if (pager->pages[page_num] == NULL) {
                printf("Tried to flush null page.\n");
                exit(EXIT_FAILURE);
        }
        off_t offset = lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET);
        if (offset == -1) {
                printf("Error seeking:%d\n", errno);
                exit(EXIT_FAILURE);
        }
        ssize_t byte_written = write(pager->file_descriptor, pager->pages[page_num], size);
        if (byte_written == -1) {
                printf("Error writing:%d", errno);
                exit(EXIT_FAILURE);
        }
}

void db_close(Table *table)
{
        Pager *pager = table->pager;
        uint32_t num_full_pages = table->num_rows / ROWS_PER_PAGE;
        for (uint32_t i = 0; i < num_full_pages; i++) {
                if (pager->pages[i] == NULL) {
                        break;
                }
                pager_flush(pager, i, PAGE_SIZE);
        }
        uint32_t num_add_rows = table->num_rows % ROWS_PER_PAGE;
        if (num_add_rows > 0) {
                uint32_t page_num = num_full_pages;
                if (pager->pages[page_num] != NULL) {
                        pager_flush(pager, page_num, num_add_rows * ROW_SIZE);
                        free(pager->pages[page_num]);
                        pager->pages[page_num] = NULL;
                }
        }
        int result = close(pager->file_descriptor);
        if (result == -1) {
                printf("Error closing db file.\n");
                exit(EXIT_FAILURE);
        }
        for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) {
                Pager *page = (Pager *) pager->pages[i];
                if (page) {
                        free(page);
                        pager->pages[i] = NULL;
                }
        }
        free(pager);
        free(table);
}

void print_prompt()
{
        printf("microdb > ");
}

int main(int argc, char **argv)
{
        InputBuffer *input_buffer = new_input_buffer();
        if (argc < 2) {
                printf("Must supply a database filename.\n");
                exit(EXIT_FAILURE);
        }
        char *filename = argv[1];
        Table *table = db_open(filename);
        while (true) {
                print_prompt();
                read_input(input_buffer);
                if (input_buffer->buffer[0] == '.') {
                        switch (do_meta_command(input_buffer,table)) {
                                case (META_COMMAND_SUCCESS):
                                        continue;
                                case (META_COMMAND_UNRECOGNIZED_COMMAND):
                                        printf("Unrecognized command '%s'\n", input_buffer->buffer);
                                        continue;
                        }
                } else {
                        Statement statement;
                        switch (prepare_statement(input_buffer, &statement)) {
                                case (PREPARE_SUCCESS):
                                        break;
                                case (PREPARE_STRING_TOO_LONG):
                                        printf("String is too long.\n");
                                        continue;
                                case (PREPARE_NEGATIVE_ID):
                                        printf("ID must be positive.\n");
                                        continue;
                                case (PREPARE_SYNTAX_ERROR):
                                        printf("Syntax error. Could not parse statement.\n");
                                        continue;
                                case (PREPARE_UNRECOGNIZED_STATEMENT):
                                        printf("Unrecognized keyword at start of '%s'.\n",
                                               input_buffer->buffer);
                                        continue;
                        }
                        switch (execute_statement(&statement, table)) {
                                case EXECUTE_SUCCESS:
                                        printf("Executed.\n");
                                        break;
                                case EXECUTE_TABLE_FULL:
                                        printf("Error: Table full.\n");
                                        break;
                        }
                }
        }
        return 0;
}

代码整体如上,代码最终在linux下编译,在windows下编译,需要改不少api,在linux下比较简单: g++ -g ./main.cpp 即可。

运行如下:

[root@localhost microdb]# ./a.out  db.mb 
microdb > select
(1,a,a@qq.com)
(2,b,b@qq.com)
(1,a,a@qq.com)
(4,rrr,rrr@qq.com)
(5,ttt,ttt@qq.com)
Executed.
microdb > insert 6 d d@qq.com
Executed.
microdb > insert 7 f f@qq.com
Executed.
microdb > select
(1,a,a@qq.com)
(2,b,b@qq.com)
(1,a,a@qq.com)
(4,rrr,rrr@qq.com)
(5,ttt,ttt@qq.com)
(6,d,d@qq.com)
(7,f,f@qq.com)
Executed.
microdb > insert 8 g g@qq.com
Executed.
microdb > select
(1,a,a@qq.com)
(2,b,b@qq.com)
(1,a,a@qq.com)
(4,rrr,rrr@qq.com)
(5,ttt,ttt@qq.com)
(6,d,d@qq.com)
(7,f,f@qq.com)
(8,g,g@qq.com)
Executed.
microdb > .exit
[root@localhost microdb]# ./a.out  db.mb 
microdb > select
(1,a,a@qq.com)
(2,b,b@qq.com)
(1,a,a@qq.com)
(4,rrr,rrr@qq.com)
(5,ttt,ttt@qq.com)
(6,d,d@qq.com)
(7,f,f@qq.com)
(8,g,g@qq.com)
Executed.
microdb > .exit

五 查看下数据库文件内容

可以通过ImHex查看db.mb内容,展示如下,id为1,注意保存在四个字节的低位,为小头模式,如果是大头模式,则要考虑到如果保存的机器为大头模式,需要考虑字节序的问题。接着a为username,下一个字段email为a@qq.com,但是整个文件相当浪费空间了。7b47405b620b09c4549e58aacbe24d7c.png

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

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

相关文章

【BBuf的CUDA笔记】六,总结 FasterTransformer Encoder(BERT) 的cuda相关优化技巧

这里总结 FasterTransformer Encoder(BERT) 的cuda相关优化技巧 解读&#xff1a;https://github.com/NVIDIA/FasterTransformer/blob/main/docs/bert_guide.md &#xff0c;优化点解读之前是翻译了下 Faster Transformer BERT 的文档&#xff0c;然后省略了运行样例等环节&…

【Datewhale一起吃瓜 Task4】啃瓜第五章

支持向量机 任务&#xff1a;找到超平面 在样本空间中&#xff0c;找到最好的超平面把样本分开&#xff0c;即找到正中间的超平面 满足 该超平面 分开了两类该超平面 最大化支持向量间隔该超平面处于 间隔中间&#xff0c;到所有支持向量距离相等 如何找&#xff1a;表示出…

从聚水潭到金蝶云星空通过接口集成数据

从聚水潭到金蝶云星空通过接口集成数据数据源系统:聚水潭聚水潭成立于2014年&#xff0c;创始人兼CEO骆海东拥有近三十年传统及电商ERP的研发和实施部署经验。聚水潭创建之初&#xff0c;以电商SaaSERP切入市场&#xff0c;凭借出色的产品和服务&#xff0c;快速获得市场的肯定…

【论文简述】Attention-Aware Multi-View Stereo(CVPR 2020)

一、论文简述 1. 第一作者&#xff1a;Keyang Luo 2. 发表年份&#xff1a;2020 3. 发表期刊&#xff1a;CVPR 4. 关键词&#xff1a;MVS、代价体、注意力机制、正则化 5. 探索动机&#xff1a; However, the feature matching results from different channels are usual…

仿写Dubbo-MyRpc

基础 在仿写Dubbo之前&#xff0c;需要了解一些技术&#xff0c;像Java反射&#xff0c;Java代理&#xff0c;Java Socket以及Dubbo相关概念。 项目结构 项目gitee地址&#xff1a;https://gitee.com/AGi_R/framework my-common 整个项目的公共资源库。存放一些公共的注解&…

拦截器、过滤器、监听器

目录一、拦截器1. 拦截器是什么?2. 设置拦截器a. 定义拦截器b. 配置加载拦截器c. 新建页面二、过滤器1. 使用原因2. Filter概念图3. Filter编程三、监听器一、拦截器 拦截器&#xff1a;必须保证页面有访问controller的操作&#xff0c;否则拦截不了 1. 拦截器是什么? 概念…

OpenWrt软路由空间扩容

文章目录预备知识OpenWrt系统固件分类EXT4固件扩容方式新建分区扩容操作步骤直接扩容操作步骤SQUASHFS固件扩容方式新建分区扩容直接扩容EFI引导固件的额外操作参考预备知识 OpenWrt系统固件分类 EXT4固件 固件包名称中包含有ext4关键字&#xff0c;可以参考固件分类关键字示意…

设计模式 - 创建型模式_建造者模式

文章目录创建型模式概述Case模拟工程Bad ImplBetter Impl &#xff08;建造者模式重构代码&#xff09;小结创建型模式 创建型模式提供创建对象的机制&#xff0c; 能够提升已有代码的灵活性和可复⽤性。 类型实现要点工厂方法定义⼀个创建对象的接⼝&#xff0c;让其⼦类⾃⼰…

编写用户注册用表单

<!-- 需求&#xff1a; 用户注册&#xff1a;用户名、密码、确认密码、性别、兴趣爱好、学历、简介 --> <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>编写用户注册用表单<…

【Linux】Linux和Window下\r与\n的区别、git命令行的使用

作者&#xff1a;小卢 专栏&#xff1a;《Linux》、《Git》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 目录 1. 回车换行符在Window下和在Linux下的区别&#xff1a; 1.1回车换行符&#xff1a;…

用友U8和旺店通企业版淘宝奇门单据接口集成

用友U8和旺店通企业奇门单据接口集成对接系统&#xff1a;旺店通企业奇门慧策最先以旺店通ERP切入商家核心管理痛点——订单管理&#xff0c;之后围绕电商经营管理中的核心管理诉求&#xff0c;先后布局流量获取、会员管理、仓库管理等其他重要经营模块。慧策的产品线从旺店通E…

实现宏offsetof()

本期介绍&#x1f356; 主要介绍&#xff1a;什么是offsetof()&#xff0c;offsetof()的用法&#xff0c;如何自己实现这个宏&#x1f440;。 offsetof其实是一个宏&#xff0c;作用是&#xff1a;能够求出指定成员相对于结构体起始地址的偏移量&#xff08;单位&#xff1a;字…

(考研湖科大教书匠计算机网络)第三章数据链路层-第一节:数据链路层概述

文章目录一&#xff1a;数据链路层概述&#xff08;1&#xff09;为什么要有数据链路层&#xff08;2&#xff09;数据链路层定义&#xff08;3&#xff09;点对点信道和广播信道二&#xff1a;数据链路层需要解决的一些问题&#xff08;1&#xff09;三个最基本问题①&#xf…

深入理解Promise之一步步教你手写Promise构造函数

目录前言一&#xff0c;手写教学1.1 基本结构1.2 resolve与reject结构搭建1.3 resolve与reject代码实现1.4 throw抛出异常改变状态1.5 promise对象状态只能转换一次1.6 then方法进行回调1.7 异步任务的回调执行1.8 执行多个回调的实现1.9 同步修改状态then方法结果返回1.10 异步…

【手写 Promise 源码】第四篇 - 翻译并理解 Promise A+ 规范

一&#xff0c;前言 上一篇&#xff0c;根据对 Promise 的分析和了解&#xff0c;实现了一个简版 Promise&#xff0c;主要涉及以下内容&#xff1a; Promise 的实现思路&#xff1b;Promise A 规范&#xff08;简版&#xff09;&#xff1b;Promise 简版实现和功能测试&…

KVM虚拟化之小型虚拟机kvmtool的使用记录

根据 kvmtool github仓库文档的描述&#xff0c;类似于QEMU&#xff0c;kvmtool是一个承载KVM Guest OS的 host os用户态虚拟机&#xff0c;作为一个纯的完全虚拟化的工具&#xff0c;它不需要修改guest os即可运行, 不过&#xff0c;由于KVM基于CPU的硬件虚拟化支持&#xff0…

读《哲学的故事》

文章目录读《哲学的故事》&#x1f6a9; 遇见&#x1f33b; 简述&#x1f33e; 部分摘抄读《哲学的故事》 一本书读过后&#xff0c;我有种脑子里又被塞进了很多新东西的感觉&#xff0c;也有种想要自我抒发、宣泄的欲望。可真到要说的时候&#xff0c;又好像无话可说。总归勉…

Java转换流(InputStreamReader/OutputStreamWriter)

文章目录概述为什么会有转换流&#xff1f;InputStreamReaderOutputStreamWriter概述 转换流是字节流到字符流的桥梁&#xff0c;在转换的过程中&#xff0c;可以指定编码。转换流也是一种处理流&#xff0c;它提供了字节流和字符流之间的转换。 转换流的两个类 InputStreamR…

1.设计模式的前奏

哪些维度评判代码质量的好坏&#xff1f; 常用的评价标准 可维护性&#xff08;maintainability&#xff09;:维护代码的成本可读性&#xff08;readability&#xff09;可扩展性&#xff08;extensibility&#xff09;&#xff1a;码应对未来需求变化的能力灵活性&#xff0…

【keepass】密码管理软件-推荐插件和相关工具合集-keepass工作流分析(自动填充、美化界面、快速添加记录、安全增强、软件和数据库维护类)

Keepass有很多已经开源的插件&#xff0c;生态良好&#xff0c;在官网有专门的插件推荐区。安装插件的方法很简单&#xff0c;直接把下载好的插件文件放在plugins文件夹内&#xff0c;重启软件即可。下面我以几大功能推荐一些keepass插件或搭配使用的浏览器扩展&#xff0c;以求…