关于在数组中存储空结尾字符串和二进制数据的说明
在您的问题中,您指定固定长度的数组可以表示:
如果您的程序无法知道数组内容代表这两种可能性中的哪一种,则如果数组内容应该表示空结尾字符串,则必须使用 null 字符填充数组的其余部分(例如使用函数 strncpy)。仅写一个空结尾字符是不够的。
这很重要,因为如果您不这样做,程序将无法知道在遇到值为 0
的字节后如何解释剩余的数据。它不会知道是否:
但是,如果您总是使用 null 字符填充终止空字符后的所有剩余字节,则您的程序将不必担心数组内容表示字符串还是二进制数据,因为它可以以相同的方式处理两者。
因此,在我的回答中,我将假设如果数组内容表示字符串,则在字符串结束后的所有剩余字节都将填充值为 0
的字节。这样,我可以假设不应忽略任何字节。
哈希表解决方案
在 Python 中,我会使用 set/hashtable/dict 而不是 list(因为查找速度非常快:O(1)):
在 C 中,也可以使用 哈希表,但您需要自己编写程序或使用已经存在的库。C 标准库不提供任何哈希函数。
以下代码将随机生成一个包含 10,000 个长度为 10 的固定长度(非空结尾)字符串的数组,其中包含字符 a
到 z
、A
到 Z
和 0
到 9
,但它还会将三个硬编码的单词放置在数组中的不同位置,以便稍后搜索这些单词,以测试搜索功能。然后,程序将把所有单词插入哈希表中,并执行几个硬编码的哈希表查找。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>
#define NUM_ARRAYS 10000
#define ARRAY_LENGTH 10
#define HASH_TABLE_SIZE 10000
struct hash_table_entry
{
struct hash_table_entry *next;
unsigned char data[ARRAY_LENGTH];
};
void fill_with_random_data( unsigned char data[NUM_ARRAYS][ARRAY_LENGTH] );
size_t hash_array( const unsigned char array[ARRAY_LENGTH] );
void build_hash_table_from_data( unsigned char data[][ARRAY_LENGTH], size_t data_length, struct hash_table_entry *hash_table[HASH_TABLE_SIZE] );
void cleanup_hash_table( struct hash_table_entry *hash_table[HASH_TABLE_SIZE] );
bool lookup( const unsigned char array[ARRAY_LENGTH], struct hash_table_entry *hash_table[HASH_TABLE_SIZE] );
bool verbose_lookup( const unsigned char array[ARRAY_LENGTH], struct hash_table_entry *hash_table[HASH_TABLE_SIZE] );
int main( void )
{
static unsigned char data[NUM_ARRAYS][ARRAY_LENGTH];
static struct hash_table_entry *hash_table[HASH_TABLE_SIZE] = {NULL};
srand( (unsigned)time( NULL ) );
printf( "Generating random data..." );
fflush( stdout );
fill_with_random_data( data );
printf( "done\n" );
fflush( stdout );
_Static_assert( NUM_ARRAYS > 500, "unexpected value" );
_Static_assert( ARRAY_LENGTH == 10, "unexpected value" );
memcpy( data[47], "abcdefghij", 10 );
memcpy( data[218], "klmnopqrst", 10 );
memcpy( data[419], "uvwxyz0123", 10 );
printf( "Building hash table..." );
fflush( stdout );
build_hash_table_from_data( data, NUM_ARRAYS, hash_table );
printf( "done\n" );
fflush( stdout );
verbose_lookup( (unsigned char *)"uvwxyz0123", hash_table );
verbose_lookup( (unsigned char *)"hsdkesldhg", hash_table );
verbose_lookup( (unsigned char *)"abcdefghij", hash_table );
verbose_lookup( (unsigned char *)"erlsodn3ls", hash_table );
verbose_lookup( (unsigned char *)"klmnopqrst", hash_table );
printf( "Performing cleanup..." );
fflush( stdout );
cleanup_hash_table( hash_table );
printf( "done\n" );
fflush( stdout );
}
void fill_with_random_data( unsigned char data[NUM_ARRAYS][ARRAY_LENGTH] )
{
static const unsigned char random_chars[62] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
for ( size_t i = 0; i < NUM_ARRAYS; i++ )
{
for ( size_t j = 0; j < ARRAY_LENGTH; j++ )
{
data[i][j] = random_chars[rand()%sizeof random_chars];
}
}
}
size_t hash_array( const unsigned char array[ARRAY_LENGTH] )
{
size_t hash = 0;
for ( size_t i = 0; i < ARRAY_LENGTH; i++ )
{
hash = hash * 7 + array[i];
}
return hash % HASH_TABLE_SIZE;
}
void build_hash_table_from_data( unsigned char data[][ARRAY_LENGTH], size_t data_length, struct hash_table_entry *hash_table[HASH_TABLE_SIZE] )
{
for ( size_t i = 0; i < data_length; i++ )
{
struct hash_table_entry **pp, *p;
size_t hash = hash_array( data[i] );
pp = &hash_table[hash];
while ( ( p = *pp ) != NULL )
pp = &p->next;
p = malloc( sizeof *p );
if ( p == NULL )
{
fprintf( stderr, "Memory allocation failure!\n" );
exit( EXIT_FAILURE );
}
p->next = NULL;
memcpy( p->data, data[i], ARRAY_LENGTH );
*pp = p;
}
}
void cleanup_hash_table( struct hash_table_entry *hash_table[HASH_TABLE_SIZE] )
{
for ( size_t i = 0; i < HASH_TABLE_SIZE; i++ )
{
struct hash_table_entry *p;
p = hash_table[i];
while ( p != NULL )
{
struct hash_table_entry *q = p;
p = p->next;
free( q );
}
}
}
bool lookup( const unsigned char array[ARRAY_LENGTH], struct hash_table_entry *hash_table[HASH_TABLE_SIZE] )
{
size_t hash;
struct hash_table_entry *p;
hash = hash_array( array );
p = hash_table[hash];
while ( p != NULL )
{
if ( memcmp( array, p->data, ARRAY_LENGTH ) == 0 )
{
return true;
}
p = p->next;
}
return false;
}
bool verbose_lookup( const unsigned char array[ARRAY_LENGTH], struct hash_table_entry *hash_table[HASH_TABLE_SIZE] )
{
printf( "Searching for %.*s...", ARRAY_LENGTH, array );
fflush( stdout );
if ( lookup( array, hash_table ) )
{
printf( "found\n" );
fflush( stdout );
return true;
}
else
{
printf( "not found\n" );
fflush( stdout );
return false;
}
}
这个程序的输出如下:
Generating random data...done
Building hash table...done
Searching for uvwxyz0123...found
Searching for hsdkesldhg...not found
Searching for abcdefghij...found
Searching for erlsodn3ls...not found
Searching for klmnopqrst...found
Performing cleanup...done
如您所见,它找到了所有明确插入的3个字符串,并且没有发现其他字符串。
在代码中,我使用
unsigned char
而不是
char
,因为在C中,
char *
通常用于传递以null结尾的字符串。因此,使用
unsigned char *
传递可以是二进制或不以null结尾的数据似乎更合适。
bsearch
解决方案
为了比较,在这里提供了一种解决方案,该方案以相同的方式生成输入数组,但使用
bsearch
而不是哈希表来搜索:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>
#define NUM_ARRAYS 10000
#define ARRAY_LENGTH 10
void fill_with_random_data( unsigned char data[NUM_ARRAYS][ARRAY_LENGTH] );
int compare_func( const void *a, const void *b );
bool verbose_search( const unsigned char array[ARRAY_LENGTH], unsigned char data[NUM_ARRAYS][ARRAY_LENGTH] );
int main( void )
{
static unsigned char data[NUM_ARRAYS][ARRAY_LENGTH];
srand( (unsigned)time( NULL ) );
printf( "Generating random data..." );
fflush( stdout );
fill_with_random_data( data );
printf( "done\n" );
fflush( stdout );
_Static_assert( NUM_ARRAYS > 500, "unexpected value" );
_Static_assert( ARRAY_LENGTH == 10, "unexpected value" );
memcpy( data[47], "abcdefghij", 10 );
memcpy( data[218], "klmnopqrst", 10 );
memcpy( data[419], "uvwxyz0123", 10 );
printf( "Sorting array..." );
fflush( stdout );
qsort( data, NUM_ARRAYS, ARRAY_LENGTH, compare_func );
printf( "done\n" );
fflush( stdout );
verbose_search( (unsigned char *)"uvwxyz0123", data );
verbose_search( (unsigned char *)"hsdkesldhg", data );
verbose_search( (unsigned char *)"abcdefghij", data );
verbose_search( (unsigned char *)"erlsodn3ls", data );
verbose_search( (unsigned char *)"klmnopqrst", data );
}
void fill_with_random_data( unsigned char data[NUM_ARRAYS][ARRAY_LENGTH] )
{
static const unsigned char random_chars[62] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
for ( size_t i = 0; i < NUM_ARRAYS; i++ )
{
for ( size_t j = 0; j < ARRAY_LENGTH; j++ )
{
data[i][j] = random_chars[rand()%sizeof random_chars];
}
}
}
int compare_func( const void *a, const void *b )
{
return memcmp( a, b, ARRAY_LENGTH );
}
bool verbose_search( const unsigned char array[ARRAY_LENGTH], unsigned char data[NUM_ARRAYS][ARRAY_LENGTH] )
{
printf( "Searching for %.*s...", ARRAY_LENGTH, array );
fflush( stdout );
if ( bsearch( array, data, NUM_ARRAYS, ARRAY_LENGTH, compare_func ) != NULL )
{
printf( "found\n" );
fflush( stdout );
return true;
}
else
{
printf( "not found\n" );
fflush( stdout );
return false;
}
}
该程序的输出如下:
Generating random data...done
Sorting array...done
Searching for uvwxyz0123...found
Searching for hsdkesldhg...not found
Searching for abcdefghij...found
Searching for erlsodn3ls...not found
Searching for klmnopqrst...found
哈希表方案可能比二分查找方案更快,前提是为输入选择了一个良好的哈希函数,以使哈希冲突的数量最小化。
char x[10] = "abcdefghij";
缺少NUL
终止符,因此它不是一个“字符串”。如果您没有限制数组大小,它将具有一个终止符 - 不要这样做。您无法在此数组上使用字符串处理函数。 - Weather Vane\0x00
,这是二进制数据,因此不是空终止符。 - Basj