字典映射是最重要的基础工具。我并没有使用hash做散列映射,而是使用了简单直接的二分查找。其主要的思路如下:
- 使用字符串的长度,进行二分查找存储
- 如果字符长度相等,使用字符串的字典顺序,进行二分查找存储
- 缓存字符串的长度,用来减少字符串长度的计算函数调用
- 元素是有序的存储在一个动态数组里
- 提供一套元素的访问方法
- 实现依赖 C 实现泛型ArrayList数组
首先看结构
typedef struct
{
const char* key;
/** strlen(key) + 1 include '\0' */
int keyLength;
/**
* ArrayStrMap value pointer
* value data copy into ArrayStrMapElement
*/
void* valuePtr;
}
ArrayStrMapElement;
typedef struct
{
ArrayList arrayList[1];
/** ArrayStrMap value type sizeof */
int typeSize;
}
ArrayStrMap;
ArrayStrMapElement 代表了存储的一个元素,valuePtr 指向传入存储的元素结构,可以看到缓存了key和key length。ArrayStrMap是用了ArrayList来存储数据,每个数据就是一个ArrayStrMapElement,typeSize是valuePtr指向元素的结构大小。
一系列元素访问接口如下:
typedef struct
{
ArrayStrMap* (*Create) (int typeSize);
void (*Init) (int typeSize, ArrayStrMap* outArrayStrMap);
ArrayStrMap* (*CreateWithCapacity) (int typeSize, int capacity);
void (*InitWithCapacity) (int typeSize, int capacity, ArrayStrMap* outArrayStrMap);
/**
* Release member memory space
*/
void (*Release) (ArrayStrMap* arrayStrMap);
/**
* Put key and value from valuePtr into ArrayStrMap
* valuePtr point to value
* return valuePtr in ArrayStrMap
*/
void* (*Put) (ArrayStrMap* arrayStrMap, const char* key, void* valuePtr);
/**
* Get valuePtr by key, if no key found return defaultValuePtr
*/
void* (*Get) (ArrayStrMap* arrayStrMap, const char* key, void* defaultValuePtr);
/**
* Set new value from valuePtr by key , return valuePtr in ArrayStrMap
*/
void* (*Set) (ArrayStrMap* arrayStrMap, const char* key, void* valuePtr);
/**
* Remove value by key
* return true success, false failed
*/
bool (*TryRemove) (ArrayStrMap* arrayStrMap, const char* key);
/**
* Clear all value, reset size 0, and keep memory space
*/
void (*Clear) (ArrayStrMap* arrayStrMap);
/**
* Insert value from valuePtr at index, the possible index may be -getIndex() - 1
* return valuePtr in ArrayStrMap
*/
void* (*InsertAt) (ArrayStrMap* arrayStrMap, const char* key, int index, void* valuePtr);
/**
* Get index of key, if negative not found then return -insertIndex - 1
* so insert index is -getIndex() - 1
*/
int (*GetIndex) (ArrayStrMap* arrayStrMap, const char* key);
/**
* Get key at index
*/
const char* (*GetKey) (ArrayStrMap* arrayStrMap, int index);
/**
* Get valuePtr at index
*/
void* (*GetAt) (ArrayStrMap* arrayStrMap, int index);
/**
* Put value from valuePtr at index
* return valuePtr in ArrayStrMap
*/
void* (*PutAt) (ArrayStrMap* arrayStrMap, int index, void* valuePtr);
/**
* Remove value at index
*/
void (*RemoveAt) (ArrayStrMap* arrayStrMap, int index);
}
_AArrayStrMap_;
extern _AArrayStrMap_ AArrayStrMap[1];
实现代码如下:
#define CheckIndex(tag, index, elements) \
ALog_A(index >= 0 && index < elements->size, "ArrayStrMap " tag " index = %d, size = %d, invalid", index, elements->size)
#define CheckInsertIndex(tag, index, elements) \
ALog_A(index >= 0 && index <= elements->size, "ArrayStrMap " tag " index = %d, size = %d, invalid", index, elements->size)
#define BinarySearch(key) \
ArrayList* elements = arrayStrMap->arrayList; \
int keyLength = (int) strlen(key) + 1; \
int high = elements->size; \
int low = -1; \
int guess = -1; \
int cmp = -1; \
\
while (high - low > 1) \
{ \
guess = (high + low) >> 1; /** not consider integer overflow */ \
ArrayStrMapElement* element = AArrayList_Get(elements, guess, ArrayStrMapElement*); \
int elementKeyLength = element->keyLength; \
\
if (elementKeyLength < keyLength) \
{ \
low = guess; \
} \
else if (elementKeyLength > keyLength) \
{ \
high = guess; \
} \
else if (elementKeyLength == keyLength) \
{ \
cmp = memcmp(element->key, key, keyLength); \
if (cmp < 0) \
{ \
low = guess; \
} \
else if (cmp > 0) \
{ \
high = guess; \
} \
else if (cmp == 0) \
{ \
/** cmp 0 means find the key */ \
break; \
} \
} \
}
// -----------------------------------------------------------------------------------------
static void* Put(ArrayStrMap* arrayStrMap, const char* key, void* valuePtr)
{
BinarySearch(key);
ALog_A(cmp != 0, "ArrayStrMap put key = %s, has already exist", key);
int typeSize = arrayStrMap->typeSize;
ArrayStrMapElement* element = (ArrayStrMapElement*)
malloc(sizeof(ArrayStrMapElement) + typeSize + keyLength);
element->keyLength = keyLength;
element->valuePtr = (char*) element + sizeof(ArrayStrMapElement);
memcpy(element->valuePtr, valuePtr, typeSize);
element->key = (char*) element->valuePtr + typeSize;
memcpy((void*) element->key, key, keyLength);
// if guess == high find guess is bigger than key in ArrayStrMap insert value here
if (guess == low)
{
// find guess is smaller than key in ArrayStrMap insert value after
// or ArrayStrMap empty
guess++;
}
AArrayList->Insert(elements, guess, &element);
return element->valuePtr;
}
static void* Get(ArrayStrMap* arrayStrMap, const char* key, void* defaultValuePtr)
{
BinarySearch(key);
return cmp == 0 ? AArrayList_Get(elements, guess, ArrayStrMapElement*)->valuePtr : defaultValuePtr;
}
static void* Set(ArrayStrMap* arrayStrMap, const char* key, void* valuePtr)
{
BinarySearch(key);
ALog_A(cmp == 0, "ArrayStrMap set key = %s, has not exist", key);
void* elementValuePtr = AArrayList_Get(elements, guess, ArrayStrMapElement*)->valuePtr;
memcpy(elementValuePtr, valuePtr, arrayStrMap->typeSize);
return elementValuePtr;
}
static bool TryRemove(ArrayStrMap* arrayStrMap, const char* key)
{
BinarySearch(key);
if (cmp == 0)
{
free(AArrayList_Get(elements, guess, ArrayStrMapElement*));
AArrayList->Remove(elements, guess);
return true;
}
return false;
}
static void Clear(ArrayStrMap* arrayStrMap)
{
ArrayList* elements = arrayStrMap->arrayList;
for (int i = 0; i < elements->size; i++)
{
free(AArrayList_Get(elements, i, ArrayStrMapElement*));
}
AArrayList->Clear(elements);
}
static void* InsertAt(ArrayStrMap* arrayStrMap, const char* key, int index, void* valuePtr)
{
ArrayList* elements = arrayStrMap->arrayList;
CheckInsertIndex("InsertAt", index, elements);
int keyLength = (int) strlen(key) + 1;
int typeSize = arrayStrMap->typeSize;
ArrayStrMapElement* element = (ArrayStrMapElement*)
malloc(sizeof(ArrayStrMapElement) + typeSize + keyLength);
element->keyLength = keyLength;
element->valuePtr = (char*) element + sizeof(ArrayStrMapElement);
memcpy(element->valuePtr, valuePtr, typeSize);
element->key = (char*) element->valuePtr + typeSize;
memcpy((void*) element->key, key, keyLength);
AArrayList->Insert(elements, index, &element);
return element->valuePtr;
}
static int GetIndex(ArrayStrMap* arrayStrMap, const char* key)
{
BinarySearch(key);
if (cmp == 0)
{
return guess;
}
if (guess == low)
{
// find guess is smaller than key in ArrayStrMap or ArrayStrMap empty
guess++;
}
// when ArrayStrMap empty guess is 0, so we -1
return -guess - 1;
}
static const char* GetKey(ArrayStrMap* arrayStrMap, int index)
{
ArrayList* elements = arrayStrMap->arrayList;
CheckIndex("GetKey", index, elements);
return AArrayList_Get(elements, index, ArrayStrMapElement*)->key;
}
static void* GetAt(ArrayStrMap* arrayStrMap, int index)
{
ArrayList* elements = arrayStrMap->arrayList;
CheckIndex("GetAt", index, elements);
return AArrayList_Get(elements, index, ArrayStrMapElement*)->valuePtr;
}
static void* PutAt(ArrayStrMap* arrayStrMap, int index, void* valuePtr)
{
ArrayList* elements = arrayStrMap->arrayList;
CheckIndex("PutAt", index, elements);
ArrayStrMapElement* element = AArrayList_Get(elements, index, ArrayStrMapElement*);
memcpy(element->valuePtr, valuePtr, arrayStrMap->typeSize);
return element->valuePtr;
}
static void RemoveAt(ArrayStrMap* arrayStrMap, int index)
{
ArrayList* elements = arrayStrMap->arrayList;
CheckIndex("RemoveAt", index, elements);
free(AArrayList_Get(elements, index, ArrayStrMapElement*));
AArrayList->Remove(elements, index);
}
static void Release(ArrayStrMap* arrayStrMap)
{
ArrayList* elements = arrayStrMap->arrayList;
for (int i = 0; i < elements->size; i++)
{
free(AArrayList_Get(elements, i, ArrayStrMapElement*));
}
AArrayList->Release(elements);
}
static void InitWithCapacity(int typeSize, int capacity, ArrayStrMap* outArrayStrMap)
{
if (capacity == 0)
{
AArrayList->Init(sizeof(ArrayStrMapElement*), outArrayStrMap->arrayList);
}
else
{
AArrayList->InitWithCapacity(sizeof(ArrayStrMapElement*), capacity, outArrayStrMap->arrayList);
}
outArrayStrMap->typeSize = typeSize;
}
static ArrayStrMap* CreateWithCapacity(int typeSize, int capacity)
{
ArrayStrMap* arrayStrMap = (ArrayStrMap*) malloc(sizeof(ArrayStrMap));
InitWithCapacity(typeSize, capacity, arrayStrMap);
return arrayStrMap;
}
static void Init(int typeSize, ArrayStrMap* outArrayStrMap)
{
InitWithCapacity(typeSize, 0, outArrayStrMap);
}
static ArrayStrMap* Create(int typeSize)
{
return CreateWithCapacity(typeSize, 0);
}
_AArrayStrMap_ AArrayStrMap[1] =
{
Create,
Init,
CreateWithCapacity,
InitWithCapacity,
Release,
Put,
Get,
Set,
TryRemove,
Clear,
InsertAt,
GetIndex,
GetKey,
GetAt,
PutAt,
RemoveAt,
};
#undef CheckIndex
#undef BinarySearch
#undef CheckInsertIndex
同样会提供宏定义的快捷操作:
/** The type is the ArrayStrMap value type */
#define ArrayStrMap(valueType) ArrayStrMap
/**
* Get key in ArrayStrMapElement by valuePtr with typeSize
*/
#define AArrayStrMap_GetKey(valuePtr, typeSize) \
((const char*) ((char*) valuePtr + typeSize))
/**
* Init constant ArrayStrMap, as element in ArrayStrmap array
* use like ArrayStrmap arr[1] = AArrayStrMap_Init(type, increment)
*/
#define AArrayStrMap_Init(type, increment) \
{ \
{ \
AArayList_Init(ArrayStrMapElement*, increment), \
sizeof(type), \
} \
}
在实际的使用中,效率不错。虽然没有hash那样的常数级的查找,但hash会有冲突的问题,以及计算hash的运算。二分查找,50个元素需要1 - 6次查找,100个元素需要 1 - 7次查询,1000个元素需要 1 - 10次查找,10000个元素需要1 - 14次查找。通常情况下都是50个元素以内的查找。