u-boot学习笔记系列(32) -- hash

https://github.com/wdfk-prog/U-boot-Study

hash 哈希 散列

时间复杂度对比

  • 哈希查找:O(1)
  • 线性查找:O(n)
  • 二分查找:O(log n)

实际性能分析

  • 哈希查找:在理想情况下,哈希查找的时间复杂度是常数时间 O(1),即无论哈希表中有多少个键,查找时间都是固定的。然而,哈希查找的性能也依赖于哈希函数的质量和哈希冲突的处理方式。
  • 线性查找:随着数据量的增加,查找时间线性增加。当数据量较小时,线性查找的性能可能还可以接受,但随着数据量的增加,性能会显著下降。
  • 二分查找:需要数据是有序的,查找时间随着数据量的增加而对数增加。对于较大的数据量,二分查找的性能优于线性查找,但不如哈希查找。

数量级对比

特性哈希表线性查找二分查找
插入时间O(1) 平均O(n)O(n)
删除时间O(1) 平均O(n)O(n)
修改时间O(1) 平均O(n)O(log n) 查找 + O(n) 修改
查找时间O(1) 平均O(n)O(log n)
内存占用较大较小中等
代码量较大较小中等

结论

  • 哈希查找:在数据量较大时(例如超过 100),哈希查找的性能明显优于线性查找和二分查找。
  • 线性查找:适用于数据量较小的场景,但随着数据量的增加,性能会显著下降。
  • 二分查找:适用于数据量较大且数据有序的场景,但不如哈希查找。

因此,当数据量超过 100 时,哈希查找通常会比线性查找和二分查找更快。

不同哈希表实现

  1. GNU C Library (glibc) u_boot Linux Kernel
    哈希表实现:hsearch 和 tsearch
    选择的路径:使用开放寻址法
    探测策略:线性探测
    哈希函数:
    负载因子:默认 0.75
    增长率:动态调整
    哈希表大小:通常使用质数。
    原因:开放寻址法在内存占用和查找性能之间取得了平衡,适用于大多数通用场景。
  2. Python (CPython)
    哈希表实现:dict 和 set
    选择的路径:使用开放寻址法
    探测策略:二次探测
    哈希函数:MurmurHash
    负载因子:默认 0.66
    增长率:2 倍增长
    哈希表大小:通常使用 2 的幂次方。
    原因:二次探测减少了哈希冲突,提高了查找和插入性能。适用于需要高效查找和插入的场景。
  3. Java (OpenJDK)
    哈希表实现:HashMap 和 HashSet
    选择的路径:使用分离链接法
    哈希函数 hashCode
    探测策略:链表和红黑树
    负载因子:默认 0.75
    增长率:2 倍增长
    哈希表大小:通常使用 2 的幂次方。
    原因:分离链接法在处理高负载因子和大量哈希冲突时表现良好,适用于需要高效查找和删除的场景。
  4. Redis
    哈希表实现:dict
    选择的路径:使用分离链接法
    哈希函数:MurmurHash
    探测策略:链表
    负载因子:默认 1.0
    增长率:2 倍增长
    哈希表大小:通常使用 2 的幂次方。
    原因:分离链接法在处理高负载因子和大量哈希冲突时表现良好,适用于需要高效查找和删除的场景。MurmurHash 提供了良好的哈希分布,减少了冲突。

增长策略

  • 动态增长:当哈希表中的键值对数量超过负载因子时,哈希表会动态增长,通常是 2 倍增长。动态增长可以减少哈希冲突,提高查找性能。
  • 渐进式扩容:在动态增长时,哈希表会逐步将键值对从旧表迁移到新表,避免一次性迁移导致的性能抖动。

负载因子

  • 负载因子:哈希表中键值对数量与表大小的比值。负载因子越大,哈希冲突越多,查找性能越低。

探测策略

  • 线性探测:当哈希冲突发生时,线性探测会逐个探测下一个位置,直到找到空槽或者查找到目标键值对。
  • 二次探测:二次探测会以二次方递增的步长探测下一个位置,减少哈希冲突。
  • 链表探测:当哈希冲突发生时,链表探测会将冲突的键值对链接在一起,形成链表。
  • 红黑树探测:当链表长度超过一定阈值时,链表会转换为红黑树,提高查找性能。

选择的路径

  • 开放寻址法:哈希表中的每个槽都可以存放键值对,当哈希冲突发生时,会探测下一个槽,直到找到空槽或者查找到目标键值对。
  • 分离链接法:哈希表中的每个槽都存放一个链表或者红黑树,当哈希冲突发生时,会将冲突的键值对链接在一起。

哈希表大小

  • 哈希表大小:哈希表中槽的数量,通常是质数。哈希表大小的选择会影响哈希冲突的发生和查找性能。
  • 质数:哈希表大小通常选择质数,可以减少哈希冲突的发生。
  • 取模运算:哈希表大小通常是 2 的幂次方,可以通过位运算替代取模运算,提高性能。

lib/hashtable.c

isprime 判断是否为素数

  1. 质数的定义:质数是大于 1 的自然数,除了 1 和它本身外,不能被其他自然数整除。例如,2、3、5、7 和 11 都是质数。
  2. div 从 3 开始,每次加 2,直到 div * div >= number时退出循环。
/*
 * 对于使用的双哈希方法,表的大小必须是质数。为了
 * 校正用户给定的表大小,我们需要进行质数测试。这个简单的
 * 算法是足够的,因为
 * a) 代码(很可能)在每次程序运行时被调用几次,并且
 * b) 数字很小,因为表必须适合核心内存
 * */
// 适用于较小的数字 快速
static int isprime(unsigned int number)
{
	/* 不会传递偶数 */
	unsigned int div = 3;

    /*这个条件确保我们只检查小于 number 平方根的因数。
    这是因为如果 number 可以被一个大于其平方根的数整除,
    那么它必然也可以被一个小于其平方根的数整除。
    因此,只需要检查到平方根即可。
    number = a * b;
    如果(a > sqrt(number) && b > sqrt(number))
    那么 a * b > sqrt(number) * sqrt(number) = number
    所以a和b中至少有一个小于sqrt(number)
    因此,只需要检查到sqrt(number)即可。
    */
	while (div * div < number && number % div != 0)
		div += 2;

	return number % div != 0;
}
// 适用于较大的数字
static int isprime(unsigned int number)
{
    if (number <= 1) return 0;
    if (number <= 3) return 1;
    if (number % 2 == 0 || number % 3 == 0) return 0;

    unsigned int sqrt_number = (unsigned int)sqrt(number);
    for (unsigned int i = 5; i <= sqrt_number; i += 6) {
        if (number % i == 0 || number % (i + 2) == 0) {
            return 0;
        }
    }
    return 1;
}

hcreate_r 创建哈希表

int hcreate_r(size_t nel, struct hsearch_data *htab)
{
	/* 将 nel 更改为不小于 nel 的第一个质数. */
	nel |= 1;		/* 使奇数 */
	while (!isprime(nel))   //不是质数
		nel += 2;   //找到下一个质数

	htab->size = nel;
	htab->filled = 0;

	/*分配内存并归零 */
	htab->table = (struct env_entry_node *)calloc(htab->size + 1,
						sizeof(struct env_entry_node));
}

hdestroy_r 销毁哈希表

/* 使用哈希表后,必须将其销毁。可以释放已用内存,并将局部静态变量标记为未使用。
*/
void hdestroy_r(struct hsearch_data *htab)
{
	int i;
	for (i = 1; i <= htab->size; ++i) {
		if (htab->table[i].used > 0) {
			struct env_entry *ep = &htab->table[i].entry;

			free((void *)ep->key);
			free(ep->data);
		}
	}
	free(htab->table);
	htab->table = NULL;
}

himport_r 键值对插入hash表

1. 从输入数据中解析环境变量,将键值对插入哈希表。
2. 如果 crlf_is_lf 为 1,则将 CRLF 转换为 LF。
3. 如果 nvars 不为 0,则跳过不需要处理的变量。
4. 如果 flag 中包含 H_NOCLEAR,则不清除哈希表。
5. 如果 flag 中不包含 H_NOCLEAR,则销毁旧的哈希表,并创建新的哈希表。
6. 如果导入的环境中没有的变量将被删除。
7. 返回 1 表示插入成功,返回 0 表示插入失败。
/*
 * crlf_is_lf: 1 - 将CRLF转换为LF 0 - 不转换
 * nvars: 不要处理的键值对数
 * vars: 不要处理的键列表
 * 扩容策略:导入整个环境时,销毁旧的哈希表,创建新的哈希表;
*/
int himport_r(struct hsearch_data *htab,
		const char *env, size_t size, const char sep, int flag,
		int crlf_is_lf, int nvars, char * const vars[])
{
	char *data, *sp, *dp, *name, *value;
	char *localvars[nvars];
	int i;

	/* Test for correct arguments.  */
	if (htab == NULL) {
		__set_errno(EINVAL);
		return 0;
	}

	/* we allocate new space to make sure we can write to the array */
	if ((data = malloc(size + 1)) == NULL) {
		debug("himport_r: can't malloc %lu bytes\n", (ulong)size + 1);
		__set_errno(ENOMEM);
		return 0;
	}
	memcpy(data, env, size);
	data[size] = '\0';
	dp = data;

	/* make a local copy of the list of variables */
	if (nvars)
		memcpy(localvars, vars, sizeof(vars[0]) * nvars);

#if CONFIG_IS_ENABLED(ENV_APPEND)   //仅允许在环境变量中追加
	flag |= H_NOCLEAR;              //不允许删除环境变量
#endif
    //导入整个环境时,销毁旧的哈希表,创建新的哈希表
    //导入变量时,不销毁
	if ((flag & H_NOCLEAR) == 0 && !nvars) {
		/*销毁旧的哈希表(如果存在) */
		if (htab->table)
			hdestroy_r(htab);
	}

	/* 如果需要,创建新的哈希表。哈希表大小的计算基于启发式方法:
	在对一些70多个现有系统的样本中,我们发现环境中每个条目的平均大小为39+字节(对于整个键值对)。
	假设每个条目的大小为8(=安全系数约为5[负载因子0.2])应该为任何现有的环境定义提供足够的安全边际,
	并且仍然允许足够多的动态添加,减少哈希冲突。
	请注意,“size”参数应该给出最大环境大小(CONFIG_ENV_SIZE)。
	这种启发式方法将导致对于大闪存环境(>8,000条目对于64 KB环境大小)来说不合理的大数字(以及内存占用),
	因此我们将其剪裁到一个合理的值。
    另一方面,当导入非常小的缓冲区时,我们需要添加一些条目以留出空闲空间。
	如果需要,这两个边界都可以在板配置文件中覆盖。*/
    //CONFIG_ENV_MIN_ENTRIES 预留的最小条目数
    //CONFIG_ENV_MAX_ENTRIES 最大条目数,
	if (!htab->table) {
		int nent = CONFIG_ENV_MIN_ENTRIES + size / 8;

		if (nent > CONFIG_ENV_MAX_ENTRIES)
			nent = CONFIG_ENV_MAX_ENTRIES;

		debug("Create Hash Table: N=%d\n", nent);

		if (hcreate_r(nent, htab) == 0) {
			free(data);
			return 0;
		}
    }
    //遍历输入数据,移除所有在换行字符(\n)前面的回车字符(\r),从而将 CRLF 序列转换为 LF 字符
	if(crlf_is_lf) {
		/*删除换行符前面的回车符*/
		unsigned ignored_crs = 0;
		for(;dp < data + size && *dp; ++dp) {
			if(*dp == '\r' &&
			   dp < data + size - 1 && *(dp+1) == '\n')
				++ignored_crs;
			else
				*(dp-ignored_crs) = *dp;
		}
		size -= ignored_crs;
		dp = data;
	}
	/* 解析环境;允许将 '\0' 和 'sep' 作为分隔符*/
	do {
		struct env_entry e, *rv;

		/*跳过前导空格*/
		while (isblank(*dp))
			++dp;

		/* 跳过注释行 */
		if (*dp == '#') {
			while (*dp && (*dp != sep))
				++dp;
			++dp;
			continue;
		}

		/* 解析名称*/
		for (name = dp; *dp != '=' && *dp && *dp != sep; ++dp)
			;

		/*处理 “name” 和 “name=” 条目 (删除 VAR) */
		if (*dp == '\0' || *(dp + 1) == '\0' ||
		    *dp == sep || *(dp + 1) == sep) {
			if (*dp == '=')
				*dp++ = '\0';
			*dp++ = '\0';	/* 终止名称*/

			debug("DELETE CANDIDATE: \"%s\"\n", name);
            //导入的环境中寻找该变量
			if (!drop_var_from_set(name, nvars, localvars))
				continue;

			if (hdelete_r(name, htab, flag))
				debug("DELETE ERROR ##############################\n");

			continue;
		}
		*dp++ = '\0';	/* terminate name */

		/* parse 值;处理转义字符 */
		for (value = sp = dp; *dp && (*dp != sep); ++dp) {
			if ((*dp == '\\') && *(dp + 1))
				++dp;
			*sp++ = *dp;
		}
		*sp++ = '\0';	/* terminate value */
		++dp;
        //没有键值对
		if (*name == 0) {
			debug("INSERT: unable to use an empty key\n");
			__set_errno(EINVAL);
			free(data);
			return 0;
		}
		/*跳过不应处理的变量 */
		if (!drop_var_from_set(name, nvars, localvars))
			continue;

		/*进入哈希表 */
		e.key = name;
		e.data = value;

		hsearch_r(e, ENV_ENTER, &rv, htab, flag);
#if !IS_ENABLED(CONFIG_ENV_WRITEABLE_LIST)  //没有定义仅可写链表的变量允许插入
		if (rv == NULL) {
			printf("himport_r: can't insert \"%s=%s\" into hash table\n",
				name, value);
		}
#endif

		debug("INSERT: table %p, filled %d/%d rv %p ==> name=\"%s\" value=\"%s\"\n",
			htab, htab->filled, htab->size,
			rv, name, value);
	} while ((dp < data + size) && *dp);	/* size check needed for text */
						/* without '\0' termination */
	debug("INSERT: free(data = %p)\n", data);
	free(data);

	if (flag & H_NOCLEAR)
		goto end;

	/* 导入的环境中没有的变量将被删除 */
	for (i = 0; i < nvars; i++) {
		if (localvars[i] == NULL)
			continue;
		//尝试从运行环境中删除变量
		if (hdelete_r(localvars[i], htab, flag))
			printf("WARNING: '%s' neither in running nor in imported env!\n", localvars[i]);
		else
			printf("WARNING: '%s' not in imported env, deleting it!\n", localvars[i]);
	}

end:
	debug("INSERT: done\n");
	return 1;		/* everything OK */
}

hsearch_r 查找哈希表

/*在内部哈希表中搜索与 item.key 匹配的条目。
如果操作是 ENV_FIND,则返回找到的条目或通过返回 NULL 来表示错误。
如果操作是 ENV_ENTER,则用 item.data 替换现有数据(如果有)。
*/
int hsearch_r(struct env_entry item, enum env_action action,
	      struct env_entry **retval, struct hsearch_data *htab, int flag)
{
	unsigned int hval;
	unsigned int count;
	unsigned int len = strlen(item.key);
	unsigned int idx;
	unsigned int first_deleted = 0;
	int ret;

	/*计算给定字符串的值。也许使用更好的方法。. */
	hval = len;
	count = len;
	while (count-- > 0) {
		hval <<= 4;
		hval += item.key[count];
	}

	/*
	 * 第一个哈希函数:
	 * 只取模但防止为零。
	 */
	hval %= htab->size;
	if (hval == 0)
		++hval;

	/* The first index tried. */
	idx = hval;
	//已经被使用,判定是墓碑还是正常使用
	if (htab->table[idx].used) {
		/*
		 * Further action might be required according to the
		 * action value.
		 */
		unsigned hval2;

		if (htab->table[idx].used == USED_DELETED)
			first_deleted = idx;
		//判定是否可以复写
		ret = _compare_and_overwrite_entry(item, action, retval, htab,
			flag, hval, idx);
		if (ret != -1)	//复写完成直接退出
			return ret;

		/*
		 * 第二个哈希函数:
		 * 如 [Knuth] 中所建议的那样
		 */
		hval2 = 1 + hval % (htab->size - 2);
		//探测序列:处理哈希冲突
		// 使用了双重哈希(Double Hashing)的方法来解决哈希冲突。双重哈希是一种开放地址法,通过使用两个不同的哈希函数来计算探测序列,从而减少冲突的概率。
		do {
			/*
			 * 因为 SIZE 是主要的,所以这保证了
			 * 逐步浏览所有可用的索引。
			 */
			if (idx <= hval2)
				idx = htab->size + idx - hval2;
			else
				idx -= hval2;

			/*
			 * 如果我们访问了所有条目,请离开循环
			 *失败。
			 */
			if (idx == hval)
				break;

			if (htab->table[idx].used == USED_DELETED
			    && !first_deleted)
				first_deleted = idx;

			//对找到的索引的键值对与需要插入的键值对进行比较
			ret = _compare_and_overwrite_entry(item, action, retval,
				htab, flag, hval, idx);
			if (ret != -1)	//找到了退出
				return ret;
		}
		//如果找到了空桶,则退出
		while (htab->table[idx].used != USED_FREE);
	}
	/* 发现了一个空桶。 */
	if (action == ENV_ENTER) {
		/*
		 * 如果表已满且需要输入另一个条目
		 * 返回错误。
		 */
		if (htab->filled == htab->size) {
			__set_errno(ENOMEM);
			*retval = NULL;
			return 0;
		}

		/*
		 * 创建新条目;
		 * 创建 item.key 和 item.data 的副本
		 */
		if (first_deleted)
			idx = first_deleted;

		htab->table[idx].used = hval;
		htab->table[idx].entry.key = strdup(item.key);
		htab->table[idx].entry.data = strdup(item.data);
		if (!htab->table[idx].entry.key ||
		    !htab->table[idx].entry.data) {
			__set_errno(ENOMEM);
			*retval = NULL;
			return 0;
		}

		++htab->filled;

		/* 这是一个新条目,因此请查找可能的回调 */
		env_callback_init(&htab->table[idx].entry);

		/* Also look for flags */
		env_flags_init(&htab->table[idx].entry);

		/* 检查权限 */
		if (htab->change_ok != NULL && htab->change_ok(
		    &htab->table[idx].entry, item.data, env_op_create, flag)) {
			debug("change_ok() rejected setting variable "
				"%s, skipping it!\n", item.key);
			_hdelete(item.key, htab, &htab->table[idx].entry, idx);
			__set_errno(EPERM);
			*retval = NULL;
			return 0;
		}

		/*如果有回调,请调用 */
		if (do_callback(&htab->table[idx].entry, item.key, item.data,
				env_op_create, flag)) {
			debug("callback() rejected setting variable "
				"%s, skipping it!\n", item.key);
			_hdelete(item.key, htab, &htab->table[idx].entry, idx);
			__set_errno(EINVAL);
			*retval = NULL;
			return 0;
        }
		/* return new entry */
		//如果中途不允许添加,则调用delete删除
		*retval = &htab->table[idx].entry;
		return 1;
	}

	__set_errno(ESRCH);
	*retval = NULL;
	return 0;
}

hmatch_r 匹配哈希表

  • 直接遍历哈希表,查找与 match 匹配的键值对。
int hmatch_r(const char *match, int last_idx, struct env_entry **retval,
	     struct hsearch_data *htab)
{
	unsigned int idx;
	size_t key_len = strlen(match);

	for (idx = last_idx + 1; idx < htab->size; ++idx) {
		if (htab->table[idx].used <= 0)
			continue;
		if (!strncmp(match, htab->table[idx].entry.key, key_len)) {
			*retval = &htab->table[idx].entry;
			return idx;
		}
	}

	__set_errno(ESRCH);
	*retval = NULL;
	return 0;
}

hdelete_r 删除哈希表中的键值对

  1. 查找键值对是否存在
  2. 如果存在,检查是否有权限删除
  3. 如果有权限删除,检查是否有回调函数
  4. 删除键值对,释放内存,并将标志设置为已删除(墓碑)
  5. 但是条目仍然存在,因为可能有其他条目使用相同的哈希值
/*
 * hdelete()
 */

/*
 * The standard implementation of hsearch(3) does not provide any way
 * to delete any entries from the hash table.  We extend the code to
 * do that.
 */

static void _hdelete(const char *key, struct hsearch_data *htab,
		     struct env_entry *ep, int idx)
{
	/* free used entry */
	debug("hdelete: DELETING key \"%s\"\n", key);
	free((void *)ep->key);
	free(ep->data);
	ep->flags = 0;
	htab->table[idx].used = USED_DELETED;

	--htab->filled;
}

int hdelete_r(const char *key, struct hsearch_data *htab, int flag)
{
	struct env_entry e, *ep;
	int idx;

	debug("hdelete: DELETE key \"%s\"\n", key);

	e.key = (char *)key;

	idx = hsearch_r(e, ENV_FIND, &ep, htab, 0);
	if (idx == 0) {
		__set_errno(ESRCH);
		return -ENOENT;	/* not found */
	}

	/* Check for permission */
	if (htab->change_ok != NULL &&
	    htab->change_ok(ep, NULL, env_op_delete, flag)) {
		debug("change_ok() rejected deleting variable "
			"%s, skipping it!\n", key);
		__set_errno(EPERM);
		return -EPERM;
	}

	/* If there is a callback, call it */
	if (do_callback(&htab->table[idx].entry, key, NULL,
			env_op_delete, flag)) {
		debug("callback() rejected deleting variable "
			"%s, skipping it!\n", key);
		__set_errno(EINVAL);
		return -EINVAL;
	}

	_hdelete(key, htab, ep, idx);

	return 0;
}

hexport_r 导出哈希表

  1. 遍历哈希表,将键值对导出为字符串。
  2. 对键排序,以便输出结果一致。
  3. 导出
ssize_t hexport_r(struct hsearch_data *htab, const char sep, int flag,
		 char **resp, size_t size,
		 int argc, char *const argv[])
{
	struct env_entry *list[htab->size];
	char *res, *p;
	size_t totlen;
	int i, n;

	/* Test for correct arguments.  */
	if ((resp == NULL) || (htab == NULL)) {
		__set_errno(EINVAL);
		return (-1);
	}

	debug("EXPORT  table = %p, htab.size = %d, htab.filled = %d, size = %lu\n",
	      htab, htab->size, htab->filled, (ulong)size);
	/*
	 * 通行证 1:
	 * 搜索已使用的条目,
	 * 保存地址并计算总长度
	 */
	for (i = 1, n = 0, totlen = 0; i <= htab->size; ++i) {

		if (htab->table[i].used > 0) {
			struct env_entry *ep = &htab->table[i].entry;
			int found = match_entry(ep, flag, argc, argv);

			if ((argc > 0) && (found == 0))
				continue;

			if ((flag & H_HIDE_DOT) && ep->key[0] == '.')
				continue;

			list[n++] = ep;

			totlen += strlen(ep->key);

			if (sep == '\0') {
				totlen += strlen(ep->data);
			} else {	/* check if escapes are needed */
				char *s = ep->data;

				while (*s) {
					++totlen;
					/* add room for needed escape chars */
					if ((*s == sep) || (*s == '\\'))
						++totlen;
					++s;
				}
			}
			totlen += 2;	/* for '=' and 'sep' char */
		}
	}

#ifdef DEBUG
	/* Pass 1a: print unsorted list */
	printf("Unsorted: n=%d\n", n);
	for (i = 0; i < n; ++i) {
		printf("\t%3d: %p ==> %-10s => %s\n",
		       i, list[i], list[i]->key, list[i]->data);
	}
#endif

	/*按键排序列表 */
	qsort(list, n, sizeof(struct env_entry *), cmpkey);

	/* Check if the user supplied buffer size is sufficient */
	if (size) {
		if (size < totlen + 1) {	/* provided buffer too small */
			printf("Env export buffer too small: %lu, but need %lu\n",
			       (ulong)size, (ulong)totlen + 1);
			__set_errno(ENOMEM);
			return (-1);
		}
	} else {
		size = totlen + 1;
	}

	/* Check if the user provided a buffer */
	if (*resp) {
		/* yes; clear it */
		res = *resp;
		memset(res, '\0', size);
	} else {
		/* no, allocate and clear one */
		*resp = res = calloc(1, size);
		if (res == NULL) {
			__set_errno(ENOMEM);
			return (-1);
		}
	}
	/*
	 * 通行证 2:
	 * 导出结果数据的排序列表
	 */
	for (i = 0, p = res; i < n; ++i) {
		const char *s;

		s = list[i]->key;
		while (*s)
			*p++ = *s++;
		*p++ = '=';

		s = list[i]->data;

		while (*s) {
			if ((*s == sep) || (*s == '\\'))
				*p++ = '\\';	/* escape */
			*p++ = *s++;
		}
		*p++ = sep;
	}
	*p = '\0';		/* terminate result */

	return size;
}

hwalk_r 对哈希表中的每个键值对执行回调

/*
 * 遍历 hash 中的所有条目,为每个条目调用 callback。
 * 这允许对每个元素执行一些通用操作。
 */
int hwalk_r(struct hsearch_data *htab, int (*callback)(struct env_entry *entry))
{
	int i;
	int retval;

	for (i = 1; i <= htab->size; ++i) {
		if (htab->table[i].used > 0) {
			retval = callback(&htab->table[i].entry);
			if (retval)
				return retval;
		}
	}

	return 0;
}

_compare_and_overwrite_entry 复写现有条目

  1. 当前桶已经被使用,检查是否有相同的哈希码
  2. 如果有相同的哈希码,检查是否有相同的键
  3. 如果有不同的键,则释放旧的键和数据,替换为新的键和数据

drop_var_from_set 判定name是否在vars[]中

/*
 * 检查变量 'name' 是否在 vars[] 中,并通过将指针设置为 NULL 来删除所有实例
 * 检查变量 'name' 是否在 vars[] 中,如果是,则将指针设置为 NULL,以删除所有实例
 * 返回1表示找到了变量,返回0表示未找到
 */
static int drop_var_from_set(const char *name, int nvars, char * vars[])
{
	int i = 0;
	int res = 0;

	/* No variables specified means process all of them */
	if (nvars == 0)
		return 1;

	for (i = 0; i < nvars; i++) {
		if (vars[i] == NULL)
			continue;
		/* If we found it, delete all of them */
		if (!strcmp(name, vars[i])) {
			vars[i] = NULL;
			res = 1;
		}
	}
	if (!res)
		debug("Skipping non-listed variable %s\n", name);

	return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值