20 散列表的查找

散列表的查找

简介:散列表(也成哈希表)是一种高效的数据结构,他可以在平均复杂度为O(1)的情况下实现查找、插入和删除操作。


哈希表的基本思想是根据关键字的值来计算其应存储的位置。这个计算过程就是通过哈希函数来实现的。

根据哈希函数H(),来确定关键字的存储位置。这个过程表示为:Hash(key)=AddrLoc(i) = H(keyi)(这里的地址可以使数组下标、索引或内存地址等)。

基本术语

  1. 散列方法(杂凑法):这是一种通过函数(散列函数)计算元素存储位置的方法。查找时,同样使用这个函数对给定的键计算地址,然后通过比较键和地址单元中元素的键来确定查找是否成功。
  2. 散列函数(杂凑函数):在散列方法中使用的转换函数,用于将键转换成数组索引(也就是元素在散列表中的位置)。
  3. 散列表(杂凑表):这是一个有限的连续存储空间,用来存储散列函数计算得到的相应散列地址的数据记录。散列表通常是一个一维数组,散列地址是数组的下标。
  4. 冲突:当两个或更多的键被散列函数映射到同一个散列地址时,就会发生冲突。冲突在散列表中是无法避免的,但我们可以通过一些方法尽可能地减少冲突。
  5. 同义词:这是指具有相同散列值(也就是通过散列函数计算得到的地址)的多个键。这些键会被映射到散列表的同一个位置,这就可能会导致冲突。

通过上述定义,我们可以知道散列表有能够快速定位关键字的存储位置的能力,下面来介绍一下散列表的主要特点

  1. 高效的查找速度:理想情况下,哈希表的查找、插入和删除操作的时间复杂度都是O(1)。这是因为哈希表通过哈希函数直接定位到元素的存储位置,避免了下线性查找的时间开销。
  2. 处理冲突:在哈希表中,可能会出现不同的关键字被哈希函数映射到同一位置的情况,这被称为哈希冲突。哈希表需要通过某些方法来处理这些冲突。
  3. 动态扩容:当哈希表中的元素超过了一定比例(通常是装载因子超过0.7或0.75)时,为了维持哈希表的性能,通常需要对哈希表进行扩容,即增加哈希表的大小。这通常需要重新哈希所有的元素,所有扩容是一个昂贵的操作。
  4. 空间换时间:哈希表通常需要比实际存储的数据更多的空间,这是为了保证哈希表的性能。(牺牲空间换时间的策略)
  5. 无序性:由于哈希函数的映射关系,哈希表中元素的存储位置与元素的值无关,因此哈希表中的元素是无序的。如果需要有序的数据结构,那么哈希表可能不是一个好的选择。

散列函数的构造方法

在构造良好的哈希函数时,需要注意一下几个要求:

  1. 哈希函数的定义域必须包括全部需要存储的关键字,而值域的范围则依赖于散列表的大小和地址范围。
  2. 哈希函数的计算速度直接影响哈希表的性能,因此哈希函数要求尽可能简单,以提高转换速度。
  3. 哈希函数要求尽可能使所有元素在哈希表中均匀分布,以减少冲突和空间浪费。
  4. 考虑关键字的长度、散列表的大小、关键字的分布情况和查找频率:这些因素会影响到哈希函数的设计和效果。

制定有效的冲突解决方案:即使哈希函数设计得很好,冲突也是无法避免的。因此,我们需要制定一个好的冲突解决方案,如链地址法或开放定址法等。

下面我们来介绍几个常用的散列函数:

直接定址法

它是最简单的一种哈希函数,它直接把关键字或者关键字的某个线性函数值作为哈希地址。即:H(key) = keyH(key) = a*key + b

  • 优点:简单,易于理解和实现。

  • 缺点:可能导致哈希表的空间利用率低。

  • 使用范围:当关键字的的全域直接对应哈希表的大小时。

除留取余法

这种方法是把关键字除以某个不大于哈希表长度的数,然后取余数作为哈希地址。即:H(key) = key MOD p,其中 p 一般取小于或等于哈希表长度的最大质数。

除留取余法的关键是选好p,使得每个关键字通过该函数转换后等概率地映射到散列空间上的任意一个地址,以减少冲突的可能。

  • 优点:简单,实现容易,对表长不敏感,适应性强。
  • 缺点:取余数操作可能会导致关键字的特性失效,从而导致分布不均匀。
  • 适用范围:当关键字的分布均匀且表长未知时。

数字分析法

这种方法是先观察关键字的分布情况,然后选择取关键字中独特的部分作为哈希地址。

平方取中法

这种方法是先把关键字平方,然后取平方数的中间几位作为哈希地址。这种方法适用于关键字本身无规律,但关键字平方后的中间几位数字分布比较均匀的情况。

折叠法

这种方法是把关键字分为几部分,然后把这几部分组合在一起,以求得哈希地址。折叠法有移位折叠和边界折叠两种。

  1. 位移折叠:位移折叠将关键字分为固定长度的几部分(通常是相等长度),然后将这些部分相加得到哈希地址。具体步骤如下:

    • 将关键字按照固定长度分割为若干部分(通常是相等长度)。
    • 将这些部分相加得到哈希地址。

    例如,假设关键字是 12345678,固定长度为 2,则可以将关键字分割为 [12, 34, 56, 78],然后将这些部分相加得到哈希地址 12 + 34 + 56 + 78 = 180。

  2. 边界折叠:边界折叠将关键字分为固定长度的几部分,但最后一部分可以是不完整的。然后,将所有部分按照一定规则相加得到哈希地址。具体步骤如下:

    • 将关键字按照固定长度分割为若干部分(通常是相等长度),最后一部分可以是不完整的。
    • 根据需要,将最后一部分的剩余位数进行填充(通常填充为0)。
      将所有部分按照一定规则相加得到哈希地址。
    • 例如,假设关键字是 12345678,固定长度为 2,则可以将关键字分割为 [12, 34, 56, 78]。由于最后一部分只有两位,不完整,我们可以将其填充为 7800。然后,将所有部分相加得到哈希地址 12 + 34 + 56 + 7800 = 7882。

随机数法

这种方法是选择一个随机函数,把关键字的随机值作为哈希地址。这种方法适用于关键字长度不等的情况。

代码实现

#include<stdio.h>
#include<stdlib.h>
// 哈希表的大小
#define HASH_SIZE 100

// 直接定址法
// H(key) = key
int directAddress(int key)
{
    return key;
}

// 除留取余法
// H(key) = key mod p (p <= m)
int division(int key)
{
    int p = 97;
    return key % p;
}

// 平方取中法
// H(key) = key^2的中间几位
int midSquare(int key)
{
    // key^2
    int square = key * key;
    // 通常除以100(移除后两位)然后再对10000取余(取中间四位)
    // 只适合四位数的键,对于其他位数的键,需要修改
    return (square / 100) % 10000;
}

// 折叠法 (位移折叠)
// H(key) = key的若干位相加,然后将这几部分组合在一起
int foldShift(int key)
{
    int part1 = key / 10000; // 取前两位
    int part2 = key % 10000; // 取后两位
    return part1 + part2;
}

// 折叠法(边界折叠)
// H(key) = 将key分为几部分,然后将这几部分组合在一起
int foldBoundary(int key) {
    int part1 = key / 10000; // 前两位
    int part2 = key % 10000; // 后两位
    return part1 + part2;
}

// 随机出法
// H(key) = 随机函数
int random(int key)
{
    return rand()%HASH_SIZE;   // 生成0到HASH_SIZE-1之间的随机数
}


int main() {
    int key = 12345678;
    printf("Direct Address: %d\n", directAddress(key));
    printf("Division: %d\n", division(key));
    printf("Mid-Square: %d\n", midSquare(key));
    printf("Fold-Shift: %d\n", foldShift(key));
    printf("Fold-Boundary: %d\n", foldBoundary(key));
    printf("Random: %d\n", random(key));
    return 0;
}

处理冲突的方法

应该注意到,任何设计出来的哈希函数都不能绝对避免冲出。对此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个“空”的哈希地址。用Hi表示处理冲突中第 i 次探测得到的哈希地址,假设得到的另一个散列地址H1仍然发生冲突,只得继续求下一个哈希地址H2,以此类推,知道Hk不发生冲突为止,则Hk为关键字在表中的地址。

开放定址法

开放地址法是指可存放新表项的空闲地址[既向它的同义词表项开放,又向它的非同义词表项开放](#解释 补充)。其数学递推公式为:

Hi = (H(key) + di) % m

其中 di 是冲突时的增量,也就是上面所提到的"它的同义词表项开放,又向它的非同义词表项开放"的过程。这个增量的选择取决于具体的冲突解决策略,通常有以下4种取法:

  1. 线性探测法:当di = 0,1,2…m-1时,成为线性探测法。这种方法的特点是:冲突发生时,顺序查看表中下一个单元(探测到表尾地址 m-1 时,下一个探测地址是表首地址0),知道找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查边全表。线性探测法可能使第 i 个散列地址的同义词存入第 i + 1 个散列地址,这样本应存入第 i + 1 个散列地址的元素就争夺第 i + 2 个散列地址的元素的地址……从而造成大量元素在相邻的散列地址上==“聚集”(或堆积)==起来,大大降低了查找效率。
  2. 平方探测法(二次探测法):它的增量序列是一个二次序列,如12,-12,22,-22,…,q2。它可以在一定程度上减少"聚簇"现象,但是增量序列的选择需要谨慎,以避免跳过某些空位置。
  3. 双散列法:当 d = Hash2(key)时,称为双散列法。需要用到两个哈希函数,在发生冲突时,使用第一个散列函数计算出一个增量值,然后将这个增量值与第二个散列函数的计算结果相结合,得到新的哈希地址。它的具体哈希函数形式:Hi = (H(key) + i * Hash2(key)) % m
  4. 伪随机序列法:它的增量序列是一个伪随机数序列。

注意:在开放定址的情形下,不能随便物理删除表中的已有元素,因为若删除元素,则会截断其他具有相同散列地址的元素的查找地址。

因此,在开放定址法中,常常使用逻辑删除的方式来标记要删除的元素,而不是直接物理删除。逻辑删除是将要删除的元素做一个特殊的标记,表示该位置上的元素已经被删除,但实际上仍然保留在哈希表中。

逻辑删除的优点是避免了截断其他具有相同散列地址的元素的查找地址,但它也会导致一个副作用。随着多次逻辑删除的执行,哈希表看起来可能会变得很满,实际上有很多未利用的位置。为了解决这个问题,需要定期维护散列表,即定期对哈希表进行重新哈希或重建,将逻辑删除的元素物理删除,从而回收未利用的位置,保持哈希表的性能。

代码实现

typedef struct
{
	int* data; // 哈希表的数据
	int size; // 哈希表的大小
} HashTable;

//创建一个新的哈希表
HashTable *createHashTable(int size)
{
	HashTable* table = (HashTable*)malloc(sizeof(HashTable));
	table->data = (int*)malloc(sizeof(int) * size);

	// 初始化哈希表
	for(int i = 0; i < size; i++)
	{
				table->data[i] = -1;  // -1表示该位置没有被使用
	}
	table->size = size;
	return table;
}
//假设哈希函数直接返回对应地址
int hash(int key)
{
    return key;
}
// 线性探测插入
void lineProbingInsert(HashTable* table, int key)
{
	int index = hash(key) % table->size;
	while (table->data[index]!=-1)  // 如果该位置已经被使用
	{
		index = (index + 1)% table->size; // 线性探测下一个位置
	}
	//找到了一个空位置,将关键字插入
	table->data[index] = key;
}

// 二次探测插入
void quadraticProbingInsert(HashTable* table, int key) {
	int index = hash(key) % table->size;
	int i = 1;
	while (table->data[index] != -1) {  // 如果这个位置已经被占用了
		index = (index + i * i) % table->size;  // 则根据二次探测法计算下一个位置
		i++;
	}
	// 找到了一个空位置,将关键字插入
	table->data[index] = key;
}

链地址法(拉链法)

基本思想:它是将哈希表中的每个位置都存储一个链表的头指针,每个链表包含具有相同散列地址的键值对。

具体步骤如下:

  1. 根据给定的哈希函数计算每个键的散列地址。
  2. 若该地址对应的链表为空,则将键值对插入该链表。
  3. 若该地址对应的链表不为空,则根据选择的冲突处理方法,在该链表上执行插入操作。可以使用链表的前插法或后插法将键值对插入到链表中。

通过链地址法,非同义词不会发生冲突,因为它们被存储在不同的链表中,避免了聚集现象。链表的空间是动态申请的,所以链地址法更适合于表长不确定的情况,可以灵活地调整链表的大小。然而,链地址法也存在一些缺点,例如在查找特定键值对时需要遍历整个链表,可能会导致较长时间的查找时间。另外链表的存储空间额外消耗了一些内存。

如下是关键字序列{19, 14, 23, 01, 68, 20, 84, 27, 55, 11,10, 79},散列函数H (key) =key%13,用拉链法处理冲突,如下图所示:

image-20230514105108151

代码实现

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

// 定义链表的节点
typedef struct Node {
    int key;  // 用于存储元素的键
    int data;  // 用于存储元素的值
    struct Node* next;  // 指向下一个节点的指针
} Node;

// 定义哈希表
typedef struct HashTable {
    int size;  // 哈希表的大小
    Node** table;  // 存储链表头节点的指针数组
} HashTable;

// 创建一个新的节点
Node* createNode(int key, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));  // 动态分配内存空间
    newNode->key = key;  // 设置键
    newNode->data = data;  // 设置值
    newNode->next = NULL;  // 初始化下一个节点的指针为NULL
    return newNode;  // 返回新节点的指针
}

// 创建一个新的哈希表
HashTable* createHashTable(int size) {
    HashTable* newTable = (HashTable*)malloc(sizeof(HashTable));  // 动态分配内存空间
    newTable->size = size;  // 设置哈希表的大小
    newTable->table = (Node**)malloc(sizeof(Node*) * size);  // 动态分配存储链表头节点的指针数组的内存空间
    for (int i = 0; i < size; i++) {
        newTable->table[i] = NULL;  // 初始化每个链表的头节点的指针为NULL
    }
    return newTable;  // 返回新哈希表的指针
}

// 散列函数
int hash(int key) {
    return key % 13;  // 返回键除以13的余数
}

// 插入元素
void insert(HashTable* table, int key, int data) {
    // 计算散列地址
    int index = hash(key);
    // 创建新节点
    Node* newNode = createNode(key, data);
    // 如果散列地址对应的链表为空,则直接插入
    if (table->table[index] == NULL) {
        table->table[index] = newNode;
    }
    else {
        // 如果散列地址对应的链表不为空,则插入到链表的头部(前插法)
    	/*
        前插法的基本步骤是:
		newNode->next = table->table[index]; 这一行是将新节点的 next 指向原来链表的头节点。这样,新节点就连接上了原来的链表。
		table->table[index] = newNode; 这一行是将新节点设置为新的头节点。因为哈希表的 table[index] 存的是链表头节点的地址,我们将它设为 newNode,即新节点成为了链表的头部。
	*/
        newNode->next = table->table[index];
        table->table[index] = newNode;
        
    }
}

// 打印哈希表
void printHashTable(HashTable* table) {
    for (int i = 0; i < table->size; i++) {
        printf("slot[%d]: ", i);
        Node* temp = table->table[i];
        while (temp) {
            printf("%d -> ", temp->data);
            temp = temp->next;
        }
        printf("NULL\n");
    }
}

int main() {
    // 创建一个大小为13的哈希表
    HashTable* table = createHashTable(13);
    // 插入元素
    insert(table, 19, 19);
    insert(table, 14, 14);
    insert(table, 23, 23);
    insert(table, 1, 1);
    insert(table, 68, 68);
    insert(table, 20, 20);
    insert(table, 84, 84);
    insert(table, 27, 27);
    insert(table, 55, 55);
    insert(table, 11, 11);
    insert(table, 10, 10);
    insert(table, 79, 79);
    // 打印哈希表
    printHashTable(table);
    return 0;
}

散列查找及性能分析

散列表的查找过程和构造散列表的过程基本一致。对于一个给定的关键字key,根据散列函数可以计算出其散列地址,算法步骤如下:

初始化:Addr = Hash(key)

  1. 检测查找表中地址为Addr的位置上是否有记录,若无记录,返回查找失败;若有记录,比较它与key 的值,若相等,则返回查找成功,否则执行步骤2
  2. 用给定的处理冲突的方法计算“下一个散列地址”,并把Addr置为此地址,转入步骤1。

例如,关键字序列{19, 14, 23, 01, 68, 20, 84, 27, 55, 11, 10, 79}按散列函数H(key) = key%13和线性探测处理冲突构造所得的散列表L如图所示:

image-20230514111511317

给定值84的查找过程为:首先求得散列地址H(84)=6,因L[6]不空且L[6]≠84,则找
第一次冲突处理后的地址H1=(6+1)%16=7,而L[7]不空且L[7]≠84,则找第二次冲突处理后
的地址H2=(6+2)%16=8, L[8]不空且L[8]=84,查找成功,返回记录在表中的序号8。

查找各关键字的比较次数如图所示:

image-20230514112424350

平均查找长度ASL为: ASL = (1*6+2+3*3+4+9)/12 = 2.5

从散列表的查找过程可见:

(1)虽然散列表在关键字与记录的存储位置之间建立了直接映像,但由于“冲突”的产生,使得散列表的查找过程仍然是-一个给定值和关键字进行比较的过程。因此,仍需要以平均查找长度作为衡量散列表的查找效率的度量。

(2)散列表的查找效率取决于三个因素:散列函数、处理冲突的方法和装填因子。

装填因子。散列表的装填因子一般记为α,定义为一个表的装满程度,即:

α = 表中记录数 n / 散列表长度 m α = {表中记录数n}/{散列表长度m} α=表中记录数n/散列表长度m

散列表的平均查找长度依赖于散列表的装填因子α,而不直接依赖于n或m。直观地看,α越大,表示装填的记录越“满”,发生冲突的可能性越大,反之发生冲突的可能性越小。

解释 补充

在哈希表的上下文中,"同义词"通常指的是具有相同哈希值的键。也就是说,如果两个键通过哈希函数计算得到的哈希值相同,那么它们就是同义词。这种情况通常会导致哈希冲突,因为哈希表无法在同一位置存储两个键值对。

相反,"非同义词"就是哈希值不同的键。理论上,非同义词应该被存储在哈希表的不同位置。然而,由于哈希表的大小是有限的,所以可能出现两个非同义词却被哈希到同一位置的情况,这也会导致哈希冲突。

在开放定址法中,如果发生哈希冲突,无论是同义词还是非同义词,都会寻找新的哈希地址来存储键值对。这就是"向它的同义词表项开放,又向它的非同义词表项开放"的含义。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习-第T5周——运动鞋品牌识别

深度学习-第T5周——运动鞋品牌识别 深度学习-第T5周——运动鞋品牌识别一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目3、查看数据 四、数据预处理1、 加载数据1、设置图片格式2、划分训练集3、划分验证集4、查看标签 2、数据可视化3、检查数据4、配置数据集 …

西瓜书读书笔记整理(三)—— 第二章 模型评估与选择

第二章 模型评估与选择 第 2 章 模型评估与选择2.1 经验误差与过拟合1. 错误率 / 精度 / 误差2. 训练误差 / 经验误差 / 泛化误差3. 过拟合 / 欠拟合4. 学习能力5. 模型选择 2.2 评估方法1. 评估方法概述2. 留出法3. 交叉验证法4. 自助法5. 调参 / 最终模型 2.3 性能度量1. 回归…

Ada语言学习(1)Basic Knowledge

文章目录 说在前头命名注释数字变量变量类型signed integersEnumerationsFloating Points 类型重用&#xff08;继承&#xff09;类型转换 运算符属性&#xff08;Attributes&#xff09;练习 说在前头 本系列教程将会通过提问的方式来完成整个学习过程&#xff0c;因为当你能…

86盒IP对讲一键报警器

86盒IP对讲一键报警器 86盒IP对讲一键报警器&#xff1a;革命性保障生命安全的利器&#xff01; 随着科技的飞速发展&#xff0c;我们的生活变得越来越方便和智能化。而86盒IP对讲一键报警器更是在这种背景下应运而生。这款产品不仅无缝对接各种手机APP&#xff0c;也可以在智…

RabbitMQ --- 惰性队列、MQ集群

一、惰性队列 1.1、消息堆积问题 当生产者发送消息的速度超过了消费者处理消息的速度&#xff0c;就会导致队列中的消息堆积&#xff0c;直到队列存储消息达到上限。之后发送的消息就会成为死信&#xff0c;可能会被丢弃&#xff0c;这就是消息堆积问题。 解决消息堆积有三种…

互联网大厂测开面试记,二面被按地上血虐,所幸Offer已到手

在互联网做了几年之后&#xff0c;去大厂“镀镀金”是大部分人的首选。大厂不仅待遇高、福利好&#xff0c;更重要的是&#xff0c;它是对你专业能力的背书&#xff0c;大厂工作背景多少会给你的简历增加几分竞争力。 如何备战面试的&#xff1f; 第一步&#xff1a;准备简历 …

vue3回到上一个路由页面

学习链接 Vue Router获取当前页面由哪个路由跳转 在Vue3的setup中如何使用this beforeRouteEnter 在这个路由方法中不能访问到组件实例this&#xff0c;但是可以使用next里面的vm访问到组件实例&#xff0c;并通过vm.$data获取组件实例上的data数据getCurrentInstance 是vue3提…

51单片机也可以移植RTOS

说起RTOS移植&#xff0c;我们首先会想到32位单片机。 那么51单片机可以移植RTOS吗&#xff1f; 我的答案是&#xff0c;只要资源够用&#xff08;ROM空间、RAM空间&#xff09;&#xff0c;可以移植。 前提是你对RTOS的实现原理非常清楚&#xff0c;并且可以自己完成移植工作…

数据可视化大屏的页面布局以及自适应

在做数据可视化大屏之前&#xff0c;我们需要考虑到页面的布局问题以及页面缩放自适应问题&#xff0c;下面分别就这两个方面讲解。 页面布局 类似这种页面区块的明显划分&#xff0c;常用的布局方式有两种&#xff1a; 1、flex布局 2、grid布局 grid布局 grid布局可以按区块…

深度学习用于医学预后-第二课第四周5-10节-为个体患者制定风险评估模型

文章目录 相对风险按风险对患者进行排序个体与基线风险吸烟者与不吸烟者年龄对风险的影响 在本课中&#xff0c;您将学习 Cox 比例风险模型(Cox Proportional Hazards Model)。您将了解 Cox 模型如何考虑患者变量来比较不同患者的风险&#xff0c;使用他们的患者概况。 但到目前…

mysql增量备份

目录 一、修改配置文件&#xff0c;开启增量备份功能 &#xff08;1&#xff09;查看是否已经开启了 &#xff08;2&#xff09;修改配置文件开启 &#xff08;3&#xff09;增量记录文件 二、还原增量备份 &#xff08;1&#xff09;修改了数据 &#xff08;2&#xff…

nginx keepalive 高可用原理和实操

文章目录 前言一、nginxkeepalive搭建高可用服务方案&#xff1f;二、方案解析1.keepalive是什么2.nginx是什么 三、keepalive与nginx环境安装四、高可用配置实例总结 前言 一、nginxkeepalive搭建高可用服务方案&#xff1f; 使用nginx-keepalived双机热备机制&#xff0c;vi…

【云计算•云原生】4.云原生之什么是Kubernetes

文章目录 Kubernetes概念Kubernetes核心概念集群podConfigMap Kubernetes架构master节点的组件worker节点组件 Kubernetes网络架构内部网络外部网络 k8s各端口含义 Kubernetes概念 K8S就是Kubernetes&#xff0c;Kubernetes首字母为K&#xff0c;末尾为s&#xff0c;中间一共有…

BEV(0)---Transformer

1 Transformer Transformer是一个Sequence to Sequence model&#xff0c;特别之处在于它大量用到了self-attention&#xff0c;替代了RNN&#xff0c;既考虑了Sequence的全局信息也解决了并行计算的问题。 1.1 self-attention&#xff1a; ①. 输入x1 ~ x4为一个sequence&…

MySQL基础(三十一)数据库其它调优策略

1 数据库调优的措施 1.1 调优的目标 尽可能 节省系统资源 &#xff0c;以便系统可以提供更大负荷的服务。&#xff08;吞吐量更大&#xff09;合理的结构设计和参数调整&#xff0c;以提高用户操作 响应的速度 。&#xff08;响应速度更快&#xff09;减少系统的瓶颈&#xf…

服务网关Gateway

前言 API 网关出现的原因是微服务架构的出现&#xff0c;不同的微服务一般会有不同的网络地址&#xff0c;而外部客户端可能需要调用多个服务的接口才能完成一个业务需求&#xff0c;如果让客户端直接与各个微服务通信&#xff0c;会有以下的问题&#xff1a; 破坏了服务无状态…

DJ6-4 文件存储空间的管理

目录 6.4.1 空闲表 1、存储空间的分配与回收 2、空闲表法的优缺点 6.4.2 空闲链表 1、空闲盘块链 2、空闲盘区链 6.4.3 位示图 1、位示图的表示 2、存储空间的分配 3、存储空间的回收 4、位示图法的优缺点 6.4.4 成组链接 1、空闲盘块的组织 plus 个人理解图…

上海亚商投顾:沪指震荡调整跌0.21% 两市成交金额不足8000亿

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日震荡调整&#xff0c;上证50午后一度跌超1%&#xff0c;以保险为首的权重板块走低。军工股逆市大涨&a…

Python基本数据类型之一——set(集合)

Python基本数据类型之一——set(集合) 一、python集合定义 集合(set)是一个无序不重复元素的序列。基本功能是进行成员关系测试和删除重复元素。 二、创建方式 在Python中&#xff0c;创建集合有两种方式&#xff1a; 一种是用一对大括号将多个用逗号分隔的数据括起来。 另一种…

【周末闲谈】超越ChatGPT?科大讯飞星火认知大模型

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言&#xff0c;模仿还是超越&#xff1f; ✨第二周 畅想AR 文章目录 前言星火名字的由来科大讯飞星火落地应用演示赶超ChatGPT的底气在哪里?“硬…