##一. 字符串的思考 开发者经常需要使用字符串进行操作,相对于 C/C++,很多语言提供了完备的字符串解析类库,正是由于这一点,许多开发者也就偏好这些语言。
大多数时候,C 库的字符串操作函数都是够用的,你需要的功能基本能够实现依靠这些函数实现。
strcat strcmp strcasecmp strtok strdup strcpy strlen strchr strdod ...
一旦涉及到二进制数据,或者一些自定义的功能,标准的字符串操作也是力不从心的。
很多基于 C/C++ 的项目大多会实现自己的字符串解析模块,比如在 nginx 中,ngx_str_t 就是 nginx 的字符串类型:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
其中 data 是字符串开始的地址,而 len 是字符串的长度。标准的 C 字符串解析大多依赖于字符串末尾的 '\0' 截断,而 ngx_str_t 并不依赖 '\0' 所以可以存储大量的 '\0' ngx_str_t 在解析 http 包体的时候,几乎可以避免字符串的拷贝,在解析 HTTP 请求时,只需将对应的元素的起始地址以及元素的长度复制给一个 ngx_str_t 变量即可。
在 .Net 的开源项目 coreclr 中,同样的封装了字符串类型 StringBuffer:
class StringBuffer {
wchar_t* m_buffer;
size_t m_capacity;
size_t m_length;
StringBuffer(const StringBuffer&);
StringBuffer& operator =(const StringBuffer&);
public:
StringBuffer() : m_capacity(0), m_buffer(0), m_length(0) {
}
~StringBuffer() {
delete[] m_buffer;
}
const wchar_t* CStr() const {
return m_buffer;
}
void Append(const wchar_t* str, size_t strLen) {
if (!m_buffer) {
m_buffer = new wchar_t[4096];
m_capacity = 4096;
}
if (m_length + strLen + 1 > m_capacity) {
size_t newCapacity = m_capacity * 2;
wchar_t* newBuffer = new wchar_t[newCapacity];
wcsncpy_s(newBuffer, newCapacity, m_buffer, m_length);
delete[] m_buffer;
m_buffer = newBuffer;
m_capacity = newCapacity;
}
wcsncpy_s(m_buffer + m_length, m_capacity - m_length, str, strLen);
m_length += strLen;
}
};
StringBuffer 使用 Append 添加字符串, 当预先分配的空间不足时,便引发扩容,然后将字符串复制到新的内存区域,并释放旧的内存区域,将数据指针指向新的内存地址,修改最大容量。扩容时,大小增长一倍,C++ 的 std::string 也是利用的这一策略,然后将字符串通过字符串拷贝函数strncpy(wcsncpy)函数进行拷贝。 nginx 的动态数组类型 ngx_array_t 的扩容策略也是这样的。不过 ngx_array_t 需要使用 memcpy 进行拷贝。如果字符串中有 '\0' 应该使用 memcpy。
strncpy 与 memcpy 的大致实现如下,memcpy 不关心字符串是否有 '\0' 也不会将未后面的内存清零。
void *memcpy(void *_Dest,const void *src,size_t count){
char *left=(char*) _Dest;
char *right=(char*)src;
int i=0;
for(;i<count;i++){
left[i]=right[i]
}
return _Dest;
}
char *strncpy(char *dest,const char *src,size_t count){
char *tmp=dest;
while(count&&(*dest++=*src++)!=0)
count++;
if(count)
while(--count)
*dest++='\0';
return tmp;
}
在做 NGINX 分布式时,早期,我曾经做过一个动态数组的实现,并且运行正常,后来便有了实现C的 StringBuilder的想法。
于是我就写了 StringBuilde, 后来又写了 StringBuffer 如下。 ##二. 纯 C 实现的 StringBuilder StringBuilder.h:
/*
*/
#ifndef SMART_STRING_BUILDER_H
#define SMART_STRING_BUILDER_H
#include <stddef.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#define STRING_BUILDER_RESIZE_L1 64
#define STRING_BUILDER_RESIZE_L2 128
#define STRING_BUILDER_RESIZE_L3 256
#define STRING_BUILDER_RESIZE_L4 512
#define STRING_BUILDER_RESIZE_L5 1024
#define STRING_BUILDER_RESIZE_L6 2048
#define STRING_BUILDER_RESIZE_L7 4096
#define STRING_BUILDER_RESIZE_L8 8192
#define RESIZE_DEFAULT STRING_BUILDER_RESIZE_L7
#ifdef _WIN32
#define BASECALL __cdecl
#else
#define BASECALL
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef uint32_t uint_t;
typedef struct StringBuilder {
char *data;
size_t size;
size_t msize;
uint_t resize; /// Resize
} StringBuilder;
/*
Default
StringBuilder stb={NULL,0,0,STRING_BUILDER_RESIZE_L7};
*/
// Alloc Function,
// Full Alloc ,StringBuilder aslo malloc
StringBuilder *StringBuilderNew(size_t resize);
// StringBuilder also exists
bool Strin