C语言实现Hash Map(2):Map代码实现详解

news2024/11/17 6:43:37

在上一节C语言实现Hash Map(1):Map基础知识入门中,我们介绍了Map的基础概念和在C++中的用法。但我写这两篇文章的目的是,能够在C语言中实现这样的一个数据结构,毕竟有时我们的项目中可能会用到Map,但是C语言库中并没有提供相关的数据结构供我们使用。所以这一节,我们就来看一下在C语言中是如何实现Map的。

  • 参考代码:https://github.com/rxi/map

文章目录

  • 1 使用实例
  • 2 代码分析
    • 2.1 map_init
      • 2.1.1 map相关数据结构
        • 2.1.1.1 变量声明
        • 2.1.1.2 map_t
        • 2.1.1.3 map_base_t
        • 2.1.1.4 map_node_t
      • 2.1.2 初始化函数
    • 2.2 通用函数分析
      • 2.2.1 map_hash:求哈希值
      • 2.2.2 map_newnode:创建新节点
      • 2.2.3 map_bucketidx:计算桶
        • 2.2.3.1 桶的数量和哈希值的关系
      • 2.2.4 map_addnode:添加节点
      • 2.2.5 map_resize:重新调整哈希表大小
    • 2.3 map_set:设置键值
        • 2.3.1 函数参数和值sizeof
        • 2.3.2 map_getref
        • 2.3.3 添加新节点
    • 2.4 map_get:获取键对应的值
    • 2.5 删除键值和遍历
  • 3 总结

1 使用实例

我们学习的过程一定是从已经成熟运用的代码中学习的,所以本文就来学习一下Github中这个已经被很多人用在项目中的map库。文件很简单,就一个map.cmap.h。我们拿到这个代码就可以直接使用的,非常简单:

#include <stdio.h>
#include <stdlib.h>
#include "map.h"

static map_str_t langMap;

int main()
{
    char *ret;
    map_init(&langMap);
    map_set(&langMap, "test", "1234");
    ret = map_get(&langMap, "test");
    if(ret != NULL)
    {
        printf("%s\r\n", ret);
    }else
    {
        printf("NULL\r\n");
    }
    return 0;
}

程序输出如下,可以看到我们初始化之后只需要设置键和值,然后使用map_get函数即可获取对应键的值了。

在这里插入图片描述

下面我们就来分析一下这里面的代码。

2 代码分析

现在,我们就基于我上面写的一个简单的例子,来分析一下代码完成了哪些操作。

2.1 map_init

2.1.1 map相关数据结构

2.1.1.1 变量声明

这里我声明了一个langMap变量:

static map_str_t langMap;

map.h文件中有声明不同的typedef:

typedef map_t(void*) map_void_t;
typedef map_t(char*) map_str_t;
typedef map_t(int) map_int_t;
typedef map_t(char) map_char_t;
typedef map_t(float) map_float_t;
typedef map_t(double) map_double_t;

这里键的类型固定是char *,上面我的例子中使用的是map_str_t这个typedef,实际上就是定义值的类型,也就是这里键和值都是char *。如果想要值是其他的类型,定义其它的类型就行了。

2.1.1.2 map_t

接下来看一下这个宏定义:

#define map_t(T)\
  struct { map_base_t base; T ref;}

可以看到就是根据用户提供的数据类型,声明一个对应的ref变量。

2.1.1.3 map_base_t

再来看一下map_base_t的数据结构,这实际上也是我们map的核心数据结构:

typedef struct {
  map_node_t **buckets;
  unsigned nbuckets, nnodes;
} map_base_t;

根据上一节我们学到的知识,通过nbucketsnnodes的名字,我们就可以猜测其含义如下:

  1. nbuckets
    • 作用:表示哈希表中桶(bucket)的数量。桶是哈希表的基本存储单元,每个桶可以包含零个或多个键值对(节点)。
    • 用途:nbuckets 用于确定将键值对分配到哪个桶中。哈希值经过处理后,取模操作决定具体的桶索引。
  2. nnodes
    • 作用:表示哈希表中当前存储的键值对(节点)的数量。
    • 用途:nnodes 用于跟踪哈希表中的实际元素数目。这个信息对于决定是否需要调整哈希表的大小(例如扩展或收缩)非常重要。当 nnodes 达到 nbuckets 的某个临界值时(如 nnodes 等于或超过 nbuckets),哈希表需要进行扩展以保持较低的碰撞率和较高的性能。
2.1.1.4 map_node_t

map.h中,声明了map_node_t

struct map_node_t;
typedef struct map_node_t map_node_t;

这个结构体的实例在map.c中:

struct map_node_t {
  unsigned hash;
  void *value;
  map_node_t *next;
  /* char key[]; */
  /* char value[]; */
};

这里的几个参数有什么作用,后面我们在代码中碰到了再分析。

2.1.2 初始化函数

这里的map_init函数实际上只是一个宏定义:

#define map_init(m)\
  memset(m, 0, sizeof(*(m)))

只是将map_str_t中各个数据结构清零,在有些RAM中,上电后初始值不一定为0,所以保险起见,还是清空一下。

2.2 通用函数分析

在分析设置键值函数之前,我们首先来学习一下后面可能会在函数中用到的一些通用的函数。

2.2.1 map_hash:求哈希值

map_hash 是一个用于计算字符串哈希值的函数。它采用了经典的 DJB2 哈希算法,这是一种快速且分布均匀的字符串哈希算法。以下是对 map_hash 函数的详细介绍:

static unsigned map_hash(const char *str) {
  unsigned hash = 5381;
  while (*str) {
    hash = ((hash << 5) + hash) ^ *str++;
  }
  return hash;
}

map_hash 函数利用 DJB2 哈希算法计算一个字符串的哈希值。DJB2 算法的核心思想是通过不断地乘以一个质数(在这里是33:左移5位+1)并进行异或操作来更新哈希值,以确保哈希值的分布均匀并减少冲突。

这个哈希函数在哈希表的实现中扮演着重要角色,因为它决定了键在哈希表中的存储位置。哈希值的质量直接影响哈希表的性能,包括查找、插入和删除操作的效率。

2.2.2 map_newnode:创建新节点

前面我们提到节点的数据结构是map_node_t,这个函数就是动态分配一个map_node_t节点并返回,实现如下:

static map_node_t *map_newnode(const char *key, void *value, int vsize) {
  map_node_t *node;
  int ksize = strlen(key) + 1;
  int voffset = ksize + ((sizeof(void*) - ksize) % sizeof(void*));
  node = MAP_MALLOC(sizeof(*node) + voffset + vsize);
  if (!node) return NULL;
  memcpy(node + 1, key, ksize);
  node->hash = map_hash(key);
  node->value = ((char*) (node + 1)) + voffset;
  memcpy(node->value, value, vsize);
  return node;
}

1、内存的分配和释放函数

大家可以移植单片机中的,比如有FreeRTOS,就可以移植vPortMallocvPortFree,我这里使用c库里的内存分配函数:

#define MAP_MALLOC malloc
#define MAP_FREE free

2、((sizeof(void*) - ksize) % sizeof(void*))

这很明显就是根据CPU的位数(sizeof(void *))来进行字节对齐。


再回来看一下map_node_t的数据结构:

struct map_node_t {
  unsigned hash;
  void *value;
  map_node_t *next;
  /* char key[]; */
  /* char value[]; */
};

这里内存分配的总大小是sizeof(*node) + voffset + vsize。其中,node为上面声明的map_node_t数据结构的总大小,然后voffset为键所占的字节对齐后的内存大小,value为值所占的内存大小。这里键和值的内存由于是不固定的,所以没有声明在结构体中,我们直接将键和值放在map_node_t的后面。

**如果后续匹配了,怎么获取键值?**获取键很容易,就在map_node_t最后,对于值的话,每次通过键设置或查值的时候,再计算一下voffset就行了。

2.2.3 map_bucketidx:计算桶

map_bucketidx 函数用于确定一个哈希值应该被放置到哈希表的哪个桶(bucket)中。很明显这个函数通过将哈希值与哈希表中的桶数量进行模运算来计算桶的索引。

  • 这里使用位与运算代替取模的话可以加快运算速度,但需要保证nbuckets的值是2n
static int map_bucketidx(map_base_t *m, unsigned hash) {
  /* If the implementation is changed to allow a non-power-of-2 bucket count,
   * the line below should be changed to use mod instead of AND */
  return hash & (m->nbuckets - 1);
}
2.2.3.1 桶的数量和哈希值的关系

在哈希表中,桶的数量(nbuckets)和哈希值之间的关系如下:

  • 哈希值:由 map_hash 函数计算得到,它是一个无符号整数,用于唯一标识一个键。
  • 桶的数量(nbuckets:表示哈希表中可用桶的数量。每个桶可以包含零个或多个键值对(节点)。
  • 桶索引:由 map_bucketidx 函数通过位与运算计算得到,用于决定哈希值被分配到哪个桶中。

2.2.4 map_addnode:添加节点

由前面的buckets的声明我们知道,buckets可以理解为map_node_t的指针的数组,数组中的每一个元素代表一个桶,每个桶也是map_node_t,里面有一个next参数,这类似于链表的数据结构,就可以连接当前桶内的所有节点。

static void map_addnode(map_base_t *m, map_node_t *node) {
  int n = map_bucketidx(m, node->hash);
  node->next = m->buckets[n];
  m->buckets[n] = node;
}

所以上面的函数就很好理解了,就是把新节点插入桶中链表的最前面。

2.2.5 map_resize:重新调整哈希表大小

map_resize 函数用于调整哈希表的大小(桶的数量)。当哈希表中的节点数超过一定比例时,通过增加桶的数量来减小冲突,提高查找、插入和删除操作的效率。具体来说,map_resize 函数将重新分配哈希表中的所有节点,使它们分布在新的桶中。下面是该函数的详细解释:

static int map_resize(map_base_t *m, int nbuckets) {
  map_node_t *nodes, *node, *next;
  map_node_t **buckets;
  int i;
  /* Chain all nodes together */
  nodes = NULL;
  i = m->nbuckets;
  while (i--) {
    node = (m->buckets)[i];
    while (node) {
      next = node->next;
      node->next = nodes;
      nodes = node;
      node = next;
    }
  }
  /* Reset buckets */
  buckets = realloc(m->buckets, sizeof(*m->buckets) * nbuckets);
  if (buckets != NULL) {
    m->buckets = buckets;
    m->nbuckets = nbuckets;
  }
  if (m->buckets) {
    memset(m->buckets, 0, sizeof(*m->buckets) * m->nbuckets);
    /* Re-add nodes to buckets */
    node = nodes;
    while (node) {
      next = node->next;
      map_addnode(m, node);
      node = next;
    }
  }
  /* Return error code if realloc() failed */
  return (buckets == NULL) ? -1 : 0;
}

简单分析一下上面的代码:

1、链表化所有节点

将所有节点串成一个单链表。遍历当前所有桶,将节点从桶中移除并加入到新的链表 nodes 中。

2、重新分配桶

使用 realloc 函数重新分配桶数组的内存,使其大小调整为新的桶数量 nbuckets。如果 realloc 成功,更新哈希表的桶指针和桶数量。

  • 注意:前面提到我们可以替换内存分配和释放的宏定义为自己的,但是这里又出现一个realloc函数,这个是在stdlib.h中的,在FreeRTOS中肯定是没有的,我们最好也不要用两种内存分配的方法,后面我们对这部分的代码做一些优化。

3、重新初始化桶

如果桶重新分配成功,则将新的桶数组初始化为 0,并将所有节点重新插入到新的桶中。通过 map_addnode 函数重新计算每个节点的桶索引,并将节点添加到对应的桶中。

2.3 map_set:设置键值

从前面的例子中,初始化之后就直接设置键值了:

map_set(&langMap, "test", "1234");

这也是这里map实现的核心,这就是一个简单的宏定义:

#define map_set(m, key, value)\
  ( map_set_(&(m)->base, key, value, sizeof(value)) )

我们主要来看一下map_set_是如何实现的:

int map_set_(map_base_t *m, const char *key, void *value, int vsize) {
  int n, err;
  map_node_t **next, *node;
  /* Find & replace existing node */
  next = map_getref(m, key);
  if (next) {
    memcpy((*next)->value, value, vsize);
    return 0;
  }
  /* Add new node */
  node = map_newnode(key, value, vsize);
  if (node == NULL) goto fail;
  if (m->nnodes >= m->nbuckets) {
    n = (m->nbuckets > 0) ? (m->nbuckets << 1) : 1;
    err = map_resize(m, n);
    if (err) goto fail;
  }
  map_addnode(m, node);
  m->nnodes++;
  return 0;
fail:
  if (node) MAP_FREE(node);
  return -1;
}
2.3.1 函数参数和值sizeof

先来看一下函数的参数,其中m就是map_base_t变量的地址,key就是键,value就是值的地址,vsize就是值的大小。现在这里有一个问题,我们使用sizeof(value)来获取值的长度,值的类型有以下几种:

typedef map_t(void*) map_void_t;
typedef map_t(char*) map_str_t;
typedef map_t(int) map_int_t;
typedef map_t(char) map_char_t;
typedef map_t(float) map_float_t;
typedef map_t(double) map_double_t;

对于void *char *int来说都没什么问题,分别返回4,字符串的长度(如果输入的是一个字符串常量的话)和4。但是:

1、char:如sizeof('c')

在C语言中,字符字面量(例如 'c')的类型是 int,而不是 char。因此,sizeof('c') 实际上会返回 sizeof(int) 的值,这通常是 4 字节(在大多数现代系统上)。这可能与期望的 sizeof(char) 返回值(通常为1字节)不同。

2、floatdouble

大家可以试一下,sizeof(3.14)sizeof(2.71828) 实际上都会返回 sizeof(double),因为在C语言中,字面值浮点数默认为 double 类型。

也就是说,这里的sizeof并不是实际的大小。


注意:在这个仓库的readme中,使用的是map_int_t类型举例的:map_set(&m, “testkey”, 123),这样明显也是不行的,因为第三个参数是void *,这里却直接传了一个数字。按照数据类型来看,这里还要声明一个int变量,然后map_set传地址才行,那这样完全变成了void *类型的了 基于此,使用map_str_t肯定是没有问题的,但是使用其它的几个数据类型,程序肯定有问题,要么编译不通过,要么通过了也内存越界,大家可以自己试一下。

也就是说,虽然这个map实现在github中是star比较多的,但是bug还是挺多的。我们有时可能还是希望可以直接设置值,而不是还要声明一个变量。所以本篇文章仅以map_str_t例子举例,实际产品用这个也是没问题的。


好了,我们暂时不纠结这个数据类型的问题,至少整个代码的map实现逻辑是没有问题的,只是兼容性这边出了点问题。下面我们开始分析map_set_函数。

2.3.2 map_getref

首先执行的是map_getref函数,下面是这个函数的实现:

static map_node_t **map_getref(map_base_t *m, const char *key)
{
  unsigned hash = map_hash(key);
  map_node_t **next;
  if (m->nbuckets > 0) {
    next = &m->buckets[map_bucketidx(m, hash)];
    while (*next) {
      if ((*next)->hash == hash && !strcmp((char*) (*next + 1), key)) {
        return next;
      }
      next = &(*next)->next;
    }
  }
  return NULL;
}

我们暂时不知道nbucketsbuckets数组在哪里设置的,还有它们的作用是什么。但是从这个函数大概可以知道,大概就是先求键的哈希值,然后去寻找一下是否有相同的键(有可能不同的键有同一个hash),如果有的话就返回这个节点指针的地址,没有的话就返回NULL。来看一下代码:

next = map_getref(m, key);
if (next) {
    memcpy((*next)->value, value, vsize);
    return 0;
}

如果该key的节点已经存在的话,就直接修改这个节点的值即可,函数直接返回。

2.3.3 添加新节点

继续分析map_set_中的代码:

    /* Add new node */
    node = map_newnode(key, value, vsize);
    if (node == NULL) goto fail;
    if (m->nnodes >= m->nbuckets) {
    n = (m->nbuckets > 0) ? (m->nbuckets << 1) : 1;
    err = map_resize(m, n);
    if (err) goto fail;
    }
    map_addnode(m, node);
    m->nnodes++;
    return 0;
fail:
    if (node) MAP_FREE(node);
    return -1;

简单分析一下:

1、节点不存在则创建节点

2、如果当前节点数量超过或等于桶的数量,计算新的桶数量**(这里设置为当前桶数量的两倍)**,然后调用 map_resize 函数调整哈希表大小。

  • 刚运行没初始化的时候,m->nbuckets设置为1

3、添加新节点到对应的桶中,并增加 nnodes 个数

  • 注意:从代码中可以看出桶的数量是我们设置节点的时候动态增加的,而且使用的是realloc函数,后续我们可以优化为上电初始化后默认有n个桶

2.4 map_get:获取键对应的值

在前面的示例代码中,设置完键值之后就可以使用map_get获取对应键的值了,返回值就是值的地址:

ret = map_get(&langMap, "test");

同样,这个函数也是一个宏定义:

#define map_get(m, key)\
  ( (m)->ref = map_get_(&(m)->base, key) )
  • 前面用宏定义map_t声明的不同数据类型的宏定义中的ref变量,只是用来临时保存值的,这个变量在其它地方都没有使用到。

所以我们就来看一下map_get_函数的实现:

void *map_get_(map_base_t *m, const char *key) {
  map_node_t **next = map_getref(m, key);
  return next ? (*next)->value : NULL;
}

前面分析过map_getref函数了:根据哈希值找到对应的桶,然后在桶中找匹配的哈希值,若哈希值匹配(有可能不同的键有同样的哈希值),再比较键,若匹配,返回键的值。

2.5 删除键值和遍历

代码中还提供了删除键值的函数map_remove,还有遍历map的函数map_itermap_next,实际上就是链表的一些操作,本文就不做分析了。

3 总结

基于本篇文章,我们已经学习到了哈希map实现的基本逻辑。另外,前面我们有提到,这个代码在值声明为其它几个数据类型的情况下,根本运行不了,或者并不方便我们开发程序(有时我们希望直接传值而不是变量地址),然后还有内存分配和初始化桶数量的地方可以优化。那么下一篇文章,我们就来解决这些问题,并优化这个代码。

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

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

相关文章

香蕉成熟度检测YOLOV8NANO

香蕉成熟度检测YOLOV8NANO&#xff0c;采用YOLOV8NANO训练&#xff0c;得到PT模型&#xff0c;然后转换成ONNX模型&#xff0c;让OEPNCV调用&#xff0c;从而摆脱PYTORCH依赖&#xff0c;支持C。python&#xff0c;安卓开发。能检测六种香蕉类型freshripe freshunripe overripe…

下载CentOS系统或者下载Ubuntu系统去哪下?

因为Centos官网是挂在国外的服务器上&#xff0c;下载镜像时相比于国内的下载速度会慢很多&#xff0c;分享国内的镜像站去阿里巴巴下载Centos镜像。 首先分享两种下载方式&#xff0c;如果只想下载Centos那么就访问方式一的下载地址即可&#xff0c;如果还想下载其他的系统&a…

Xfce4桌面背景和桌面图标消失问题解决@FreeBSD

问题&#xff1a;Xfce4桌面背景和桌面图标消失 以前碰到过好几次桌面背景和桌面图标消失&#xff0c;整个桌面除了上面一条和下面中间的工具条&#xff0c;其它地方全是黑色的问题&#xff0c;但是这次重启之后也没有修复&#xff0c;整个桌面乌黑一片&#xff0c;啥都没有&am…

[书生·浦语大模型实战营]——第二节:轻松玩转书生·浦语大模型趣味 Demo

1. 部署InternLM2-Chat-1.8B 模型进行智能对话 1.1配置环境 创建开发机 Intern Studio 官网网址&#xff1a;https://studio.intern-ai.org.cn/ 进入官网后&#xff0c;选择创建开发机&#xff0c;填写 开发机名称 后&#xff0c;点击 选择镜像 使用 Cuda11.7-conda 镜像&am…

楼房vr安全逃生模拟体验让你在虚拟环境中亲身体验火灾的紧迫与危险

消防VR安全逃生体验系统是深圳VR公司华锐视点引入了前沿的VR虚拟现实、web3d开发和多媒体交互技术&#xff0c;为用户打造了一个逼真的火灾现场应急逃生模拟演练环境。 相比传统的消防逃生模拟演练&#xff0c;消防VR安全逃生体验系统包含知识讲解和模拟实训演练&#xff0c;体…

码蹄集部分题目(2024OJ赛16期;单调栈集训+差分集训)

&#x1f9c0;&#x1f9c0;&#x1f9c0;单调栈集训 &#x1f96a;单调栈 单调递增栈伪代码&#xff1a; stack<int> st; for(遍历数组) {while(栈不为空&&栈顶元素大于当前元素)//单调递减栈就是把后方判断条件变为小于等于即可{栈顶元素出栈;//同时进行其他…

C语言笔记22 •结构体•

C语言结构体 1.结构体类型的声明 struct Stu { char name[ 20 ]; // 名字 int age; // 年龄 char sex[ 5 ]; // 性别 char id[ 20 ]; // 学号 }; 2.结构体变量的创建和初始化 #include <stdio.h>// 定义一个结构体类型 Point struct Point {int x;int y; };i…

【三个数的最大乘积】python

三层循环必然超时&#xff0c;是的 hhh,换种思路&#xff0c;就很巧 class Solution:def maximumProduct(self, nums: List[int]) -> int:nums.sort()mxnums[-1]*nums[-2]*nums[-3]if nums[0]*nums[1]*nums[-1]>mx:mxnums[0]*nums[1]*nums[-1]return mx

装修:尽显个性品味

家&#xff0c;是心灵的港湾&#xff0c;也是生活的舞台。装修&#xff0c;不仅是对空间的改造&#xff0c;更是对生活态度的诠释。无论是温馨的北欧风&#xff0c;还是华丽的欧式古典&#xff0c;或是简约的现代感&#xff0c;我们的专业团队都能为您量身打造。每一个细节&…

分布式数据库HBase入门指南

目录 概述 HBase 的主要特点包括: HBase 的典型应用场景包括: 访问接口 1. Java API: 2. REST API: 3. Thrift API: 4. 其他访问接口: HBase 数据模型 概述 该模型具有以下特点&#xff1a; 1. 面向列: 2. 多维: 3. 稀疏: 数据存储: 数据访问: HBase 的数据模型…

01-02.Vue的常用指令(二)

01-02.Vue的常用指令&#xff08;二&#xff09; 前言v-model&#xff1a;双向数据绑定v-model举例&#xff1a;实现简易计算器Vue中通过属性绑定为元素设置class 类样式引入方式一&#xff1a;数组写法二&#xff1a;在数组中使用三元表达式写法三&#xff1a;在数组中使用 对…

YOLOv10尝鲜测试五分钟极简配置

最近清华大学团队又推出YOLOv10&#xff0c;真是好家伙了。 安装&#xff1a; pip install supervision githttps://github.com/THU-MIG/yolov10.git下载权重&#xff1a;https://github.com/THU-MIG/yolov10/releases/download/v1.0/yolov10n.pt 预测&#xff1a; from ult…

2024年最全的信息安全、数据安全、网络安全标准分享(可下载)

以上是资料简介和目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a;https://t.zsxq.com/Gz1a0

基于SpringBoot+Vue的人事管理系统

引言 目前,人事管理的系统大都是CS架构的大型系统,很少有面向机关,事业单位内部的基于BS架构的微型人事系统,因此.开发一个基于BS架构的人事信息管理系统是非常必要的.但是基于BS架构的人事系统对于安全是一个大的考验点.在人事信息系统中,功能需简单清晰,可操作性强,其次安全…

结构化开发方法(数据流图)

一、系统设计基本原理 二、系统总体结构设计 三、数据流图 数据流图

数据库(4)——DDL数据库操作

SQL标准没有提供修改数据库模式定义的语句&#xff0c;用户想修改次对象只能将它删除后重建。 查询 查询所有数据库&#xff1a; SHOW DATABASES; 在安装完MySQL数据库之后&#xff0c;自带了4个数据库&#xff0c;如下图&#xff1a; 创建数据库 数据库的创建语言为 CREATE…

web学习笔记(五十六)

目录 1.绑定类名和style 1.1 绑定类名 1.1.1 绑定单个类名 1.1.2 绑定多个类名 1.2 style相关知识 2. vue的响应式原理 3. v-once 4.本地搭建Vue单页应用 4.1 安装Vue脚手架 4.2 安装对应的包文件 4.3 运行项目 1.绑定类名和style 1.1 绑定类名 1.1.1 绑定单个类名…

浅析FAT32文件系统

本文通过实验测试了FAT文件系统的存储规律&#xff0c;并且探究了部分可能的文件隐藏方法。 实验背景 现有一块硬盘&#xff08;U盘&#xff09;&#xff0c;其中存在两个分区&#xff0c;分别为FAT32和NTFS文件系统分区。 在FAT分区中存在如下文件&#xff1a; 现需要阅读底…

智研未来,直击 AI DevOps,阿里云用户交流日杭州站来啦!

在这个技术日新月异的时代&#xff0c;云上智能化 DevOps 正以前所未有的速度推动企业创新边界&#xff0c;重塑软件开发的效率与品质。 为深入探索这一变革之路&#xff0c;诚邀您参与我们的专属闭门技术沙龙&#xff0c;携手开启一场关于云上智能化 DevOps 的挑战、实践与未…

【全网最全】2024电工杯数学建模B题完整版保奖思路代码模型(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片链接&#xff0c;那是您获取资料的入口&#xff01; 【全网最全】2024电工杯数学建模B题53页成品论文完整matlab、py代码19建模过程代码数据等&#xff08;后续会更新&#xff09;「首先来看看目前已有的…