C语言数据库管理系统示例:文件操作、内存管理、错误处理与动态数据库设计 栈和堆的内存分配

devtools/2024/12/23 3:21:57/

C语言的管理数据库完整的小型系统示例:

#include <stdio.h>      // 引入标准输入输出库,提供printf等功能
#include <assert.h>      // 引入断言库,用于调试时检查条件
#include <stdlib.h>      // 引入标准库,提供malloc、free、exit等功能
#include <errno.h>       // 引入错误号库,用于获取系统调用的错误号
#include <string.h>      // 引入字符串处理库,提供strncpy等字符串操作函数#define MAX_DATA 512     // 定义常量MAX_DATA为512,用于指定名字和邮件的最大长度
#define MAX_ROWS 100     // 定义常量MAX_ROWS为100,表示数据库最多可以有100条记录// 地址结构体,表示每条记录
struct Address {int id;              // 记录的唯一标识符int set;             // 标记记录是否已被设置,0表示未设置,1表示已设置char name[MAX_DATA]; // 存储联系人姓名的数组,最大长度为MAX_DATAchar email[MAX_DATA];// 存储联系人电子邮件的数组,最大长度为MAX_DATA
};// 数据库结构体,表示整个数据库
struct Database {struct Address rows[MAX_ROWS];  // 数组存储每一条记录,最多MAX_ROWS条记录
};// 连接结构体,用于表示数据库连接,包括文件和数据库内存数据
struct Connection {FILE *file;             // 文件指针,指向数据库文件struct Database *db;    // 指向内存中数据库数据的指针
};// 错误处理函数,打印错误信息并终止程序
void die(const char *message)
{if (errno) {                 // 如果有错误号perror(message);         // 打印系统错误信息} else {printf("ERROR: %s\n", message);  // 否则打印自定义的错误消息}exit(1);  // 退出程序
}// 打印地址(联系人的)信息
void Address_print(struct Address *addr)
{printf("%d %s %s\n", addr->id, addr->name, addr->email);  // 打印id、name和email
}// 从文件中加载数据库
void Database_load(struct Connection *conn)
{int rc = fread(conn->db, sizeof(struct Database), 1, conn->file);  // 从文件读取数据库if (rc != 1) die("Failed to load database.");  // 如果读取失败,调用die函数输出错误信息并退出
}// 打开数据库文件,返回连接对象
struct Connection *Database_open(const char *filename, char mode)
{struct Connection *conn = malloc(sizeof(struct Connection));  // 为Connection结构体分配内存if (!conn) die("Memory error");  // 如果内存分配失败,调用die函数conn->db = malloc(sizeof(struct Database));  // 为Database结构体分配内存if (!conn->db) die("Memory error");  // 如果内存分配失败,调用die函数if (mode == 'c') {  // 如果是创建模式,打开文件进行写操作conn->file = fopen(filename, "w");} else {  // 如果是读取模式,打开文件进行读写操作conn->file = fopen(filename, "r+");if (conn->file) {  // 如果文件打开成功Database_load(conn);  // 从文件加载数据库}}if (!conn->file) die("Failed to open the file");  // 如果文件无法打开,调用die函数退出return conn;  // 返回连接对象
}// 关闭数据库连接并释放相关资源
void Database_close(struct Connection *conn)
{if (conn) {  // 如果连接对象不为空if (conn->file) fclose(conn->file);  // 关闭文件if (conn->db) free(conn->db);  // 释放数据库内存free(conn);  // 释放连接对象内存}
}// 将数据库内容写入文件
void Database_write(struct Connection *conn)
{rewind(conn->file);  // 将文件指针回到文件开头int rc = fwrite(conn->db, sizeof(struct Database), 1, conn->file);  // 将数据库内容写入文件if (rc != 1) die("Failed to write database.");  // 如果写入失败,调用die函数rc = fflush(conn->file);  // 刷新文件流,确保所有数据都写入文件if (rc == -1) die("Cannot flush database.");  // 如果刷新失败,调用die函数
}// 创建数据库,初始化每条记录
void Database_create(struct Connection *conn)
{int i = 0;for (i = 0; i < MAX_ROWS; i++) {struct Address addr = {.id = i, .set = 0};  // 初始化每条记录,id为i,set为0conn->db->rows[i] = addr;  // 将初始化的记录赋值给数据库的相应位置}
}// 设置数据库某条记录的信息
void Database_set(struct Connection *conn, int id, const char *name, const char *email)
{struct Address *addr = &conn->db->rows[id];  // 获取指定id的记录if (addr->set) die("Already set, delete it first");  // 如果记录已设置,则报错addr->set = 1;  // 设置记录标记为已设置// 复制名字到记录char *res = strncpy(addr->name, name, MAX_DATA);if (!res) die("Name copy failed");  // 如果复制失败,调用die函数// 复制电子邮件到记录res = strncpy(addr->email, email, MAX_DATA);if (!res) die("Email copy failed");  // 如果复制失败,调用die函数
}// 获取并打印指定id的记录
void Database_get(struct Connection *conn, int id)
{struct Address *addr = &conn->db->rows[id];  // 获取指定id的记录if (addr->set) {  // 如果记录已设置Address_print(addr);  // 打印记录} else {die("ID is not set");  // 如果记录未设置,则报错}
}// 删除指定id的记录
void Database_delete(struct Connection *conn, int id)
{struct Address addr = {.id = id, .set = 0};  // 初始化一个删除的记录,id为id,set为0conn->db->rows[id] = addr;  // 将该记录写入数据库
}// 列出所有已设置的记录
void Database_list(struct Connection *conn)
{int i = 0;struct Database *db = conn->db;for (i = 0; i < MAX_ROWS; i++) {struct Address *cur = &db->rows[i];  // 获取当前记录if (cur->set) {  // 如果记录已设置Address_print(cur);  // 打印记录}}
}// 主函数:根据命令行参数执行相应的数据库操作
int main(int argc, char *argv[])
{if (argc < 3) die("USAGE: ex17 <dbfile> <action> [action params]");  // 如果参数不足,报错并退出char *filename = argv[1];  // 获取数据库文件名char action = argv[2][0];  // 获取操作类型(c=create, g=get, s=set, d=del, l=list)struct Connection *conn = Database_open(filename, action);  // 打开数据库文件并返回连接对象int id = 0;if (argc > 3) id = atoi(argv[3]);  // 如果有id参数,转换为整数if (id >= MAX_ROWS) die("There's not that many records.");  // 如果id超过最大记录数,报错并退出switch (action) {case 'c':  // 如果操作类型是创建数据库Database_create(conn);  // 创建数据库Database_write(conn);   // 写入文件break;case 'g':  // 如果操作类型是获取记录if (argc != 4) die("Need an id to get");  // 如果缺少id参数,报错Database_get(conn, id);  // 获取并打印指定id的记录break;case 's':  // 如果操作类型是设置记录if (argc != 6) die("Need id, name, email to set");  // 如果缺少参数,报错Database_set(conn, id, argv[4], argv[5]);  // 设置指定id的记录Database_write(conn);  // 写入文件break;case 'd':  // 如果操作类型是删除记录if (argc != 4) die("Need id to delete");  // 如果缺少id参数,报错Database_delete(conn, id);  // 删除指定id的记录Database_write(conn);  // 写入文件break;case 'l':  // 如果操作类型是列出记录Database_list(conn);  // 列出所有已设置的记录break;default:  // 如果操作类型无效,报错die("Invalid action, only: c=create, g=get, s=set, d=del, l=list");}Database_close(conn);  // 关闭数据库连接并释放资源return 0;  // 返回0表示程序正常结束
}

对以上的文件操作库函数进行解析:

1.fopen 函数

FILE *fopen(const char *filename, const char *mode);

功能:fopen 是 C 标准库中用于打开文件的函数,用于指定文件的打开模式,并返回一个指向文件的指针,允许程序读写文件
参数:
        filename:要打开的文件名(包括路径,如果文件不在当前目录)。
        mode:打开文件的模式,指定如何访问文件。
返回值: 如果文件成功打开,返回指向文件的 FILE 指针,后续的文件操作都通过这个指针进行。 如果文件无法打开(例如文件不存在,权限不足等),返回 NULL,此时可以通过 errno 或 perror 获取错误信息。


2. freadfwrite 函数

int fread(void *ptr, size_t size, size_t count, FILE *stream);

功能:从指定的文件流 stream 中读取 count 个对象,每个对象大小为 size 字节,并将它们存储在 ptr 指向的内存区域中。
参数:
        ptr:指向存储读取数据的缓冲区的指针。
        size:每个对象的大小(以字节为单位)。
        count:要读取的对象数量。
        stream:指向 FILE 类型的文件指针,指定要读取的文件。
返回值:返回实际读取的对象数量。如果返回值小于 count,则表示发生了错误或文件结束。

int fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

功能:向指定的文件流 stream 写入数据。写入 count 个对象,每个对象大小为 size 字节,数据来自 ptr 指向的内存区域。
参数:
        ptr:指向要写入数据的缓冲区的指针。
        size:每个对象的大小(以字节为单位)。
        count:要写入的对象数量。
        stream:指向 FILE 类型的文件指针,指定要写入的文件。
返回值:返回实际写入的对象数量。如果返回值小于 count,则表示发生了错误。

3. rewind 函数

void rewind(FILE *stream);

功能:将文件指针 stream 移动到文件的开头。
参数:
        stream:指向要操作的文件流。
返回值:没有返回值。成功时,文件指针指向文件的起始位置。如果发生错误,ferror 会返回一个非零值。

4. fflush 函数

int fflush(FILE *stream);

功能:刷新输出缓冲区,将缓冲区中的数据强制写入到文件中。
参数:
        stream:指向要刷新的文件流。如果 stream 为 NULL,则刷新所有输出流的缓冲区。
返回值: 如果成功,返回 0。 如果发生错误,返回 EOF,并设置 errno 来指示错误原因。

 C 标准库中用于处理错误的相关机制

1、errno

        errno 是一个全局变量,定义在 <errno.h> 头文件中,它用于指示上一个系统调用或库函数发生的错误类型。
        作用:每当某个函数(特别是系统调用或库函数)失败时,它会将一个错误代码存储在 errno 中。该错误代码是一个整数,代表具体的错误类型。
         errno 的值:它的值会随着错误发生而改变。因此,在调用可能设置 errno 的函数后,需要检查其值并根据错误代码提供相应的处理。
常见的 errno 错误代码:
        EINVAL:无效的参数。
        ENOENT:没有该文件或目录。
        ENOMEM:内存不足。
        EACCES:权限拒绝。
        EIO:输入输出错误。
        EPERM:操作不允许。
        EIO:硬件错误。

示例:

#include <stdio.h>
#include <errno.h>
#include <string.h>int main() {FILE *file = fopen("nonexistent_file.txt", "r");if (file == NULL) {// 打印错误代码printf("Error code: %d\n", errno);// 打印错误描述printf("Error description: %s\n", strerror(errno));}return 0;
}

2、perror

   perror 是 C 标准库中用于打印 errno 的错误信息的函数,它会根据 errno 中的错误代码输出相应的错误描述。它输出的信息通常包括用户自定义的前缀字符串(可选)和错误消息。
函数原型:

void perror(const char *s);

        参数 s:是用户提供的字符串,如果提供了,perror 会先输出该字符串,再输出对应的错误描述;如果不提供,直接输出错误描述。
        输出:perror 会输出一个标准的错误信息,格式为:<prefix>: <error_description>。

堆和栈的内存分配

        堆就是你电脑中的剩余内存,你可以通过malloc访问它来获取更多内存,OS会使用内部函数为你注册一块内存区域,并且返回指向它的指针。当你使用完这片区域时,你应该使用free把它交还给OS,使之能被其它程序复用。如果你不这样做就会导致程序“泄露”内存,但是Valgrind会帮你监测这些内存泄露。

        栈是一个特殊的内存区域,它储存了每个函数的创建的临时变量,它们对于该函数为局部变量。它的工作机制是,函数的每个函数都会“压入”栈中,并且可在函数内部使用。它是一个真正的栈数据结构,所以是后进先出的。这对于main中所有类似char sectionint id的局部变量也是相同的。使用栈的优点是,当函数退出时C编译器会从栈中“弹出”所有变量来清理。这非常简单,也防止了栈上变量的内存泄露。

        理清内存的最简单的方式是遵守这条原则:如果你的变量并不是从malloc中获取的,也不是从一个从malloc获取的函数中获取的,那么它在栈上。

        而除了堆和栈之外,还有数据段(Data Segment),用于存储程序中的全局变量、静态变量和常量数据,分为:已初始化数据段(存储已初始化的全局变量和静态变量)未初始化数据段(BSS段)存储未初始化的全局变量和静态变量,程序运行时会将它们初始化为 0。特点:数据段在程序加载时会被加载到内存中,并且生命周期与程序相同
 

附加题

  • die函数需要接收conn变量作为参数,以便执行清理并关闭它。
    void die(struct Connection *conn,const char *message)
    {if(errno) {perror(message);} else {printf("ERROR: %s\n", message);}Database_close(conn);exit(1);
    }
  • 修改代码,使其接收参数作为MAX_DATAMAX_ROWS,将它们储存在Database结构体中,并且将它们写到文件。这样就可以创建任意大小的数据库
     
    // 修改struct Database
    struct Database {int max_data;  // 动态存储最大数据大小int max_rows;  // 动态存储最大行数struct Address *rows;  // 动态分配存储的地址
    };// 修改struct Connection *Database_open函数
    struct Connection *Database_open(const char *filename, char mode, int max_data, int max_rows)
    {struct Connection *conn = malloc(sizeof(struct Connection));if (!conn) die(conn, "Memory error");conn->db = malloc(sizeof(struct Database));if (!conn->db) die(conn, "Memory error");// 存储MAX_DATA和MAX_ROWSconn->db->max_data = max_data;conn->db->max_rows = max_rows;// 动态分配数据库条目conn->db->rows = malloc(max_rows * sizeof(struct Address));if (!conn->db->rows) die(conn, "Memory error for rows");if (mode == 'c') {conn->file = fopen(filename, "w");} else {conn->file = fopen(filename, "r+");if (conn->file) {Database_load(conn);}}if (!conn->file) die(conn, "Failed to open the file");return conn;
    }// 修改主函数中输入参数格式if (argc < 5) die(NULL, "USAGE: ex17 <dbfile> <action> <max_data> <max_rows> [action params]");char *filename = argv[1];char action = argv[2][0];int max_data = atoi(argv[3]);int max_rows = atoi(argv[4]);struct Connection *conn = Database_open(filename, action, max_data, max_rows);int id = 0;if (argc > 5) id = atoi(argv[5]);if (id >= conn->db->max_rows) die(conn, "There's not that many records.");switch (action) {case 'c':Database_create(conn);Database_write(conn);break;case 'g':if (argc != 6) die(conn, "Need an id to get");Database_get(conn, id);break;case 's':if (argc != 7) die(conn, "Need id, name, email to set");Database_set(conn, id, argv[6], argv[7]);Database_write(conn);break;case 'd':if (argc != 6) die(conn, "Need id to delete");Database_delete(conn, id);Database_write(conn);break;case 'l':Database_list(conn);break;default:die(conn, "Invalid action, only: c=create, g=get, s=set, d=del, l=list");}
    
  • 数据库添加更多操作,比如find
     
    // 添加相应内容
    void Database_find(struct Connection *conn,const char *name)
    {struct Database *db = conn->db;for (int i = 0; i < db->max_rows; i++) {struct Address *cur = &db->rows[i];if (cur->set && strcmp(cur->name,name) == 0) {Address_print(db,i);}}}case 'f':if (argc != 7) die(conn, "Need an id to get");Database_find(conn,argv[6]);break;
    
  • 查询C如何打包结构体,并且试着弄清楚为什么你的文件是相应的大小。看看你是否可以计算出结构体添加一些字段之后的新大小。

    在 C 语言中,打包(packing)结构体是指调整结构体的内存布局,减少或消除由于字节对齐造成的内存填充(padding)。字节对齐的目的是提高处理器访问内存时的效率,尤其是在处理较大数据时。

    1. 结构体的内存布局(Padding)

    通常情况下,编译器会根据结构体中成员的类型对内存进行对齐。每个数据类型都有一个对齐要求(alignment requirement),例如:
    char 通常对齐到 1 字节边界(sizeof(char) = 1)
    int 通常对齐到 4 字节边界(sizeof(int) = 4)
    如果结构体成员没有按照其对齐要求对齐,编译器会自动插入填充字节(padding)来确保每个成员按照适当的对齐要求存储。
    例如:
    #include <stdio.h>struct MyStruct {char a;int b;short c;
    };int main() {printf("Size of MyStruct: %lu\n", sizeof(struct MyStruct));return 0;
    }// 输出结果为: Size of MyStruct: 12
    
    布局如下:
    | a(char) | padding(3 bytes) | b(int) | c(short) | padding(2 bytes) |
    
    2. 结构体打包(packing)
    为了打包结构体(消除内存中的填充字节),我们可以使用编译器特性,例如 #pragma pack 指令(具体依赖于编译器)或 __attribute__((packed))
    例如:
    #include <stdio.h>#pragma pack(1)  // 设置结构体按 1 字节对齐struct MyStruct {char a;int b;short c;
    };int main() {printf("Size of MyStruct: %lu\n", sizeof(struct MyStruct));return 0;
    }// 输出结果:Size of MyStruct: 7
    
  • Address添加一些字段,使它们可被搜索。

    类似find功能
     
  • 编写一个shell脚本来通过以正确顺序运行命令执行自动化测试。提示:在bash顶端使用使用set -e,使之在任何命令发生错误时退出。
     
    #!/bin/bash# 启用错误退出模式
    #set -e# 定义文件和路径
    C_SOURCE_FILE="ex17.c"
    C_EXEC_FILE="ex17"
    TEST_DB_FILE="testdb.dat"# 编译 C 代码
    echo "Compiling the C code..."
    gcc -Wall -g $C_SOURCE_FILE -o $C_EXEC_FILE# 测试 1: 创建数据库
    echo "Running test 1: Creating a new database..."
    ./$C_EXEC_FILE $TEST_DB_FILE c# 测试 2: 设置数据
    echo "Running test 2: Setting data for ID 0..."
    ./$C_EXEC_FILE $TEST_DB_FILE s 0 "Joe Alex" "joe@example.com"# 测试 3: 获取数据
    echo "Running test 3: Getting data for ID 0..."
    ./$C_EXEC_FILE $TEST_DB_FILE g 0# 测试 4: 列出所有数据
    echo "Running test 4: Listing all entries..."
    ./$C_EXEC_FILE $TEST_DB_FILE l# 测试 5: 删除数据
    echo "Running test 5: Deleting data for ID 0..."
    ./$C_EXEC_FILE $TEST_DB_FILE d 0# 测试 6: 确认删除
    echo "Running test 6: Verifying data for ID 0..."
    ./$C_EXEC_FILE $TEST_DB_FILE g 0# 清理测试文件
    echo "Cleaning up after tests..."
    rm  $TEST_DB_FILEecho "All tests passed successfully!"
    
  • 尝试重构程序,使用单一的全局变量来储存数据库连接。这个新版本和旧版本比起来如何?

    使用全局变量,在重构后,struct Connection *conn 被定义为一个全局变量。所有与数据库相关的操作都直接访问这个全局变量,无需再通过函数参数传递连接。函数 Database_open、Database_close、Database_write、Database_create 等不再需要接受 conn 作为参数,因为全局变量 conn 可以直接访问。
    新版本与旧版本的对比:
    优点:
    简洁性: 代码中函数签名变得更简洁,不再需要通过参数传递 conn,而是直接使用全局变量。
    易于访问: 在多个函数中直接访问全局变量,避免了多次传递参数的麻烦。

    缺点:
    可维护性差: 使用全局变量会让程序的状态管理变得更复杂,特别是在多线程或多任务环境中。函数内部无法独立操作数据库连接,而是依赖于全局状态。
    难以测试: 单一的全局变量增加了测试的复杂性。测试时,可能需要手动初始化和清理全局变量,尤其是在多个测试用例之间共享状态时。
    灵活性降低: 使用全局变量意味着函数之间没有明确的数据流动关系,导致代码的解耦性降低,后期的扩展可能变得更加困难。

    如果程序的规模较小,且没有复杂的模块化需求,使用全局变量可以简化代码,减少函数参数的传递。 但在大型项目或需要高可维护性和高可测试性的项目中,尽量避免使用全局变量,或者将其限制在必要的范围内。
     
  • 搜索“栈数据结构”,并且在你最喜欢的语言中实现它,然后尝试在C中实现。

    使用python实现栈:
    class Stack:def __init__(self):# 使用列表来存储栈中的元素self.items = []def is_empty(self):# 判断栈是否为空return len(self.items) == 0def push(self, item):# 将元素推入栈中self.items.append(item)def pop(self):# 从栈中弹出一个元素,并返回该元素if not self.is_empty():return self.items.pop()else:raise IndexError("pop from empty stack")def peek(self):# 查看栈顶的元素,不移除它if not self.is_empty():return self.items[-1]else:raise IndexError("peek from empty stack")def size(self):# 返回栈中元素的个数return len(self.items)# 示例用法
    if __name__ == "__main__":stack = Stack()stack.push(1)stack.push(2)stack.push(3)print(f"栈顶元素: {stack.peek()}")  # 输出 3print(f"栈的大小: {stack.size()}")  # 输出 3print(f"弹出的元素: {stack.pop()}")  # 输出 3print(f"栈的大小: {stack.size()}")  # 输出 2
    

    使用C语言实现栈:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>#define MAX_SIZE 100  // 栈的最大容量// 定义栈结构体
    typedef struct {int items[MAX_SIZE];  // 存储栈元素的数组int top;  // 栈顶指针
    } Stack;// 初始化栈
    void init_stack(Stack *s) {s->top = -1;  // 初始化时栈为空
    }// 判断栈是否为空
    bool is_empty(Stack *s) {return s->top == -1;
    }// 判断栈是否已满
    bool is_full(Stack *s) {return s->top == MAX_SIZE - 1;
    }// 将元素推入栈中
    void push(Stack *s, int item) {if (is_full(s)) {printf("栈满,无法推入元素\n");return;}s->items[++(s->top)] = item;
    }// 从栈中弹出一个元素
    int pop(Stack *s) {if (is_empty(s)) {printf("栈空,无法弹出元素\n");exit(1);  // 异常退出}return s->items[(s->top)--];
    }// 查看栈顶元素
    int peek(Stack *s) {if (is_empty(s)) {printf("栈空,无法查看栈顶元素\n");exit(1);  // 异常退出}return s->items[s->top];
    }// 返回栈的大小
    int size(Stack *s) {return s->top + 1;
    }int main() {Stack stack;init_stack(&stack);push(&stack, 1);push(&stack, 2);push(&stack, 3);printf("栈顶元素: %d\n", peek(&stack));  // 输出 3printf("栈的大小: %d\n", size(&stack));  // 输出 3printf("弹出的元素: %d\n", pop(&stack));  // 输出 3printf("栈的大小: %d\n", size(&stack));  // 输出 2return 0;
    }
    

    Python和C实现对比
    在Python中,栈实现依赖于列表(list),Python自动处理内存管理。栈的大小可以动态变化。 在C中,我们必须手动管理内存(栈大小),并且栈的最大容量是预定义的(MAX_SIZE)。如果需要动态调整大小,需要重新分配内存。  Python的代码更加简洁和灵活,因为它具有自动内存管理和内建的动态数据结构(如list)。 C需要更多的手动内存管理和对边界条件的检查,代码相对繁琐。


http://www.ppmy.cn/devtools/144564.html

相关文章

微信小程序-生成骨架屏

文章目录 微信小程序-生成骨架屏概述步骤 微信小程序-生成骨架屏 概述 骨架屏是页面的一个空白版本&#xff0c;通常会在页面完全渲染之前&#xff0c;通过一些灰色的区块大致勾勒出轮廓&#xff0c;待数据加载完成后&#xff0c;再替换成真实的内容。微信小程序提供了自动生…

基于Spring Boot的网络海鲜市场系统

一、系统背景与目的 随着互联网的快速发展和电子商务的普及&#xff0c;海鲜市场也逐渐向线上转移。传统的海鲜销售方式存在信息不对称、交易效率低、管理成本高等问题。为了解决这些问题&#xff0c;基于Spring Boot的网络海鲜市场系统应运而生。该系统旨在通过线上平台&…

基于DockerCompose搭建Redis主从哨兵模式

linux目录结构 内网配置 哨兵配置文件如下&#xff0c;创建3个哨兵配置文件 # sentinel26379.conf sentinel26380.conf sentinel26381.conf 内容如下 protected-mode no sentinel monitor mymaster redis-master 6379 2 sentinel down-after-milliseconds mymaster 60000 s…

Python发送带key的kafka消息

在Python中发送带有键&#xff08;key&#xff09;的Kafka消息&#xff0c;通常会使用confluent-kafka或kafka-python这样的库。这里我将分别展示如何使用这两个库来实现这个功能。 ### 使用 confluent-kafka 首先&#xff0c;确保你已经安装了confluent-kafka库。如果没有安装…

面试小札:闪电五连鞭_6

1. synchronized和ReentrantLock的区别 - 实现机制 - synchronized 是Java中的关键字&#xff0c;它是基于JVM内部实现的。JVM会自动对 synchronized 修饰的方法或代码块进行加锁和解锁操作。例如&#xff1a; public class SynchronizedExample { public synchronized …

【单片机原理】第1章 微机基础知识,运算器,控制器,寄存器,微机工作过程,数制转换

关注作者了解更多 我的其他CSDN专栏 过程控制系统 工程测试技术 虚拟仪器技术 可编程控制器 工业现场总线 数字图像处理 智能控制 传感器技术 嵌入式系统 复变函数与积分变换 单片机原理 线性代数 大学物理 热工与工程流体力学 数字信号处理 光电融合集成电路…

基于 Node.js 的开源轻量简洁 API 调试工具:Hoppscotch

对于使用过 Postman 或 ApiPost 的开发者来说&#xff0c;可能找过比其更加简洁、轻量的API调试工具。本篇文章就为大家推荐一款开源、免费、轻量、简洁、美观的API调试工具&#xff1a;Hoppscotch。 项目介绍 Hoppscotch 是一款基于 Node.js 开发的免费、开源、且便捷美观的 …

Pytorch | 从零构建ResNet对CIFAR10进行分类

Pytorch | 从零构建ResNet对CIFAR10进行分类 CIFAR10数据集ResNet核心思想网络结构创新点优点应用 ResNet结构代码详解结构代码代码详解BasicBlock 类ResNet 类ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数 训练过程和测试结果代码汇总resnet.pytrain.pytest.py 前…