Skip to content

PostgreSQL StringInfoData 数据结构详解

摘要

StringInfoData 是 PostgreSQL 中广泛使用的动态字符串缓冲区数据结构,用于高效地构建和管理可变长度的字符串。本文基于 PostgreSQL 源码(src/include/lib/stringinfo.hsrc/common/stringinfo.c),详细剖析 StringInfo 的设计原理、内部实现、使用模式和最佳实践。


1. 设计背景与特点

1.1 为什么需要 StringInfo?

传统的 C 字符串处理方式存在以下问题:

c
// 传统方式的问题
char buffer[1024];
sprintf(buffer, "%s", str1);
strcat(buffer, str2);  // 需要计算长度
strcat(buffer, str3);  // 容易缓冲区溢出

StringInfoData 提供了:

  • 自动内存管理: 根据需要自动扩展缓冲区
  • 长度跟踪: 始终知道当前字符串长度
  • 安全追加: 不会发生缓冲区溢出
  • 二进制支持: 可以存储任意二进制数据
  • 内存上下文集成: 与 PostgreSQL 内存管理系统无缝集成

1.2 核心特性

特性说明
最大长度1GB (MaxAllocSize)
内存分配使用 palloc() (后端) 或 malloc() (前端)
字符串类型可存储 C 字符串或二进制数据
终止符data[len] 始终为 '\0'
游标支持内置游标用于顺序读取

2. 数据结构详解

2.1 StringInfoData 定义

c
// src/include/lib/stringinfo.h:46-54
typedef struct StringInfoData
{
    char   *data;      // 字符串缓冲区指针
    int     len;       // 当前字符串长度 (不含终止符)
    int     maxlen;    // 已分配缓冲区大小 (含终止符)
    int     cursor;    // 游标位置,用于顺序读取
} StringInfoData;

typedef StringInfoData *StringInfo;  // 指针类型别名

2.2 内存布局图解

┌─────────────────────────────────────────────────────────────────────┐
│                     StringInfoData 内存布局                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  StringInfo str = initStringInfo();                                │
│                                                                     │
│  初始状态 (1024 字节):                                              │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │ data ──────► ┌──────────────────────────────────────────────┐ │ │
│  │             │ [0]              [len]        [maxlen-1]      │ │ │
│  │             │  '\0'                                              │ │ │
│  │             │  ▲                                               │ │ │
│  │             │  └─ len = 0, data[0] = '\0'                    │ │ │
│  │             └──────────────────────────────────────────────┘ │ │
│  │                                                                 │ │
│  │  len = 0        maxlen = 1024                                  │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  追加 "hello" 后:                                                  │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │ data ──────► ┌──────────────────────────────────────────────┐ │ │
│  │             │ 'h'  'e'  'l'  'l'  'o'  '\0'              │ │ │
│  │             │ [0]  [1]  [2]  [3]  [4]  [5]                 │ │ │
│  │             │                         ▲                       │ │ │
│  │             │                         └─ len = 5           │ │ │
│  │             └──────────────────────────────────────────────┘ │ │
│  │                                                                 │ │
│  │  len = 5        maxlen = 1024                                  │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  追加更多数据后需要扩展:                                            │
│  ┌────────────────────────────────────────────────────────────────┐ │
│  │ data ──────► ┌──────────────────────────────────────────────┐ │ │
│  │ (旧)        │ 'h'  'e'  'l'  'l'  'o'  ...               │ │ │
│  │             └──────────────────────────────────────────────┘ │ │
│  │                   │                                            │ │
│  │                   │ repalloc (可能移动)                        │ │
│  │                   ▼                                            │ │
│  │             ┌──────────────────────────────────────────────┐ │ │
│  │             │ 'h'  'e'  'l'  'l'  'o'  ...  '\0'         │ │ │
│  │             │ [0]  [1]  [2]  [3]  [4]  ...  [2047]         │ │ │
│  │             └──────────────────────────────────────────────┘ │ │
│  │                                                                 │ │
│  │  len = 5        maxlen = 2048 (扩展后)                        │ │
│  └────────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2.3 字段说明

字段类型说明
datachar *指向字符串缓冲区的指针
lenint当前字符串长度 (不包括终止符 \0)
maxlenint缓冲区总大小 (包括终止符空间)
cursorint游标位置,用于顺序读取操作

2.4 重要约束

c
// 始终满足的不变式:
// 1. data != NULL
// 2. maxlen > len  (除非是只读 StringInfo)
// 3. data[len] == '\0'  (终止符)
// 4. 0 <= len <= maxlen <= MaxAllocSize

3. 创建与初始化

3.1 四种创建方式

c
// 方式 1: makeStringInfo()
// 数据缓冲区和结构体都使用 palloc 分配
StringInfo makeStringInfo(void);

// 方式 2: initStringInfo()
// 结构体是栈变量,仅数据缓冲区使用 palloc 分配
void initStringInfo(StringInfo str);

// 方式 3: initReadOnlyStringInfo()
// 只读模式,不复制数据,不对缓冲区负责
void initReadOnlyStringInfo(StringInfo str, char *data, int len);

// 方式 4: initStringInfoFromString()
// 从已存在的 palloc 缓冲区初始化
void initStringInfoFromString(StringInfo str, char *data, int len);

3.2 创建示例

c
// 方式 1: makeStringInfo()
// 推荐用于需要返回 StringInfo 的场景
StringInfo
build_query(void)
{
    StringInfo result = makeStringInfo();
    appendStringInfoString(result, "SELECT * FROM users");
    return result;  // 调用者负责释放
}

// 方式 2: initStringInfo()
// 推荐用于局部使用
void
process_data(void)
{
    StringInfoData buf;
    initStringInfo(&buf);
    
    appendStringInfo(&buf, "Hello, %s!", "World");
    printf("%s\n", buf.data);
    
    // 栈变量,自动释放
}

// 方式 3: 只读模式
void
parse_buffer(char *input, int len)
{
    StringInfoData buf;
    initReadOnlyStringInfo(&buf, input, len);
    
    // 只能读取,不能追加或重置
    // 不会复制数据,直接使用原缓冲区
    // 适用于性能敏感的只读场景
}

// 方式 4: 从现有缓冲区
void
wrap_existing_buffer(char *existing)
{
    StringInfoData buf;
    int len = strlen(existing);
    
    initStringInfoFromString(&buf, existing, len);
    
    // 可以追加,但会影响原始缓冲区
    // 适用于复用已分配内存的场景
}

3.3 内部实现

c
// makeStringInfo 实现
StringInfo makeStringInfo(void)
{
    StringInfo res;
    
    res = (StringInfo) palloc(sizeof(StringInfoData));
    initStringInfo(res);
    
    return res;
}

// initStringInfo 实现
void initStringInfo(StringInfo str)
{
    int size = 1024;  // 初始缓冲区大小
    
    str->data = (char *) palloc(size);
    str->maxlen = size;
    resetStringInfo(str);  // 设置 len=0, data[0]='\0'
}

4. 字符串追加操作

4.1 追加函数一览

函数说明性能
appendStringInfo()格式化追加较慢
appendStringInfoString()追加字符串
appendStringInfoChar()追加单个字符
appendStringInfoCharMacro()追加单个字符(宏)最快
appendStringInfoSpaces()追加空格
appendBinaryStringInfo()追加二进制数据
appendBinaryStringInfoNT()追加二进制(无终止符)最快

4.2 格式化追加

c
// 类似于 sprintf + strcat,但更安全
void appendStringInfo(StringInfo str, const char *fmt, ...) 
    __attribute__((format(printf, 2, 3)));

// 使用示例
StringInfo buf = makeStringInfo();
appendStringInfo(buf, "SELECT %s FROM %s WHERE ", 
    "\"name\"", "\"users\"");
appendStringInfo(buf, "id = %d", 42);
// 结果: SELECT "name" FROM "users" WHERE id = 42

4.3 字符串追加

c
// 追加 null 终止的字符串,比 appendStringInfo 更高效
void appendStringInfoString(StringInfo str, const char *s);

// 使用示例
appendStringInfoString(buf, "Hello");
appendStringInfoString(buf, " ");
appendStringInfoString(buf, "World");
// 等价于: appendStringInfo(buf, "%s %s %s", "Hello", "", "World");
// 但性能更好

4.4 字符追加

c
// 追加单个字符
void appendStringInfoChar(StringInfo str, char ch);

// 高性能宏版本 (关键路径使用)
#define appendStringInfoCharMacro(str, ch) \
    (((str)->len + 1 >= (str)->maxlen) ? \
     appendStringInfoChar(str, ch) : \
     (void)((str)->data[(str)->len] = (ch), \
            (str)->data[++(str)->len] = '\0'))

// 使用示例
for (int i = 0; i < 10; i++)
{
    appendStringInfoChar(buf, 'a' + i);
    appendStringInfoChar(buf, ',');
}

4.5 二进制追加

c
// 追加任意二进制数据 (含终止符)
void appendBinaryStringInfo(StringInfo str, 
                           const void *data, int datalen);

// 追加二进制数据 (不含终止符)
void appendBinaryStringInfoNT(StringInfo str, 
                              const void *data, int datalen);

// 使用示例
// 处理二进制数据
const char binary_data[] = {0x01, 0x02, 0x03, 0x00, 0xFF};
appendBinaryStringInfo(buf, binary_data, sizeof(binary_data));
// 注意: 如果数据可能包含 '\0',使用此函数

// 处理带长度的文本
const char *text = get_string_ptr();
int len = get_string_length();
appendBinaryStringInfo(buf, text, len);

4.6 空格追加

c
// 追加指定数量的空格
void appendStringInfoSpaces(StringInfo str, int count);

// 使用示例
appendStringInfoString(buf, "SELECT");
appendStringInfoSpaces(buf, 1);  // 1 个空格
appendStringInfoString(buf, "name");
appendStringInfoSpaces(buf, 2);  // 2 个空格
appendStringInfoString(buf, "FROM users");
// 结果: "SELECT name  FROM users"

5. 内存管理与重置

5.1 重置 StringInfo

c
// 清空内容但保留缓冲区
void resetStringInfo(StringInfo str);

// 内部实现
void resetStringInfo(StringInfo str)
{
    Assert(str->maxlen != 0);  // 不能重置只读 StringInfo
    
    str->data[0] = '\0';
    str->len = 0;
    str->cursor = 0;
}

// 使用示例
void process_multiple_items(void)
{
    StringInfo buf = makeStringInfo();
    
    for (int i = 0; i < 1000; i++)
    {
        resetStringInfo(buf);  // 清空,复用缓冲区
        appendStringInfo(buf, "Item %d processed", i);
        process_item(buf->data);
    }
    
    destroyStringInfo(buf);
}

5.2 预扩展缓冲区

c
// 确保缓冲区有足够空间 (提前分配)
void enlargeStringInfo(StringInfo str, int needed);

// 使用示例
void build_large_result(void)
{
    StringInfo buf = makeStringInfo();
    
    // 预估需要 100KB,提前分配避免多次扩展
    enlargeStringInfo(buf, 100 * 1024);
    
    // 频繁追加操作
    for (int i = 0; i < 10000; i++)
    {
        appendStringInfo(buf, "data item %d\n", i);
    }
    
    destroyStringInfo(buf);
}

5.3 销毁 StringInfo

c
// 释放 StringInfo 及其缓冲区 (makeStringInfo 创建的)
void destroyStringInfo(StringInfo str);

// 使用示例
StringInfo buf = makeStringInfo();
// ... 使用 buf ...
destroyStringInfo(buf);  // 必须调用!

// 如果使用 initStringInfo (栈变量),不需要销毁
void foo(void)
{
    StringInfoData buf;
    initStringInfo(&buf);
    // ... 使用 buf ...
    // 函数返回时 buf 自动销毁
}

5.4 内存管理注意事项

c
// 重要: 内存上下文行为
/*
 * enlargeStringInfo 使用 repalloc(),这意味着:
 * - 缓冲区保持在 initStringInfo 调用时的内存上下文中
 * - 即使当前上下文改变,缓冲区仍然有效
 * - 这对于 PostgreSQL 的内存管理至关重要!
 */

// 正确做法: 在正确的上下文中初始化
void correct_usage(MemoryContext ctx)
{
    MemoryContext old = MemoryContextSwitchTo(ctx);
    
    StringInfo buf = makeStringInfo();  // 在 ctx 中分配
    // ... 使用 buf ...
    
    MemoryContextSwitchTo(old);
    destroyStringInfo(buf);  // 在同一个 ctx 中释放
}

6. 缓冲区扩展机制

6.1 扩展策略

c
// enlargeStringInfo 实现分析
void enlargeStringInfo(StringInfo str, int needed)
{
    int newlen;
    
    // 验证请求
    if (needed < 0)
        ereport(ERROR, ...);  // 负数请求无效
        
    if (needed >= MaxAllocSize - str->len)
        ereport(ERROR, ...);  // 超出最大限制
        
    needed += str->len + 1;  // 总需求空间
    
    // 如果已有空间足够,直接返回
    if (needed <= str->maxlen)
        return;
        
    // 倍增策略: 2x 扩展
    newlen = 2 * str->maxlen;
    while (needed > newlen)
        newlen = 2 * newlen;
        
    // 限制在最大允许大小
    if (newlen > MaxAllocSize)
        newlen = MaxAllocSize;
        
    // 重新分配
    str->data = (char *) repalloc(str->data, newlen);
    str->maxlen = newlen;
}

6.2 扩展示例

初始状态: maxlen = 1024, len = 1000

追加 50 字节后:
  needed = 50 + 1000 + 1 = 1051
  needed <= maxlen (1024)? 否
  newlen = 2 * 1024 = 2048
  1051 <= 2048? 是,扩展到 2048

追加 1500 字节后:
  needed = 1500 + 1050 + 1 = 2551
  2551 <= 2048? 否
  newlen = 2 * 2048 = 4096
  2551 <= 4096? 是,扩展到 4096

6.3 性能优化建议

c
// 1. 如果知道最终大小,提前扩展
StringInfo buf = makeStringInfo();
enlargeStringInfo(buf, expected_size);

// 2. 批量追加优于多次小追加
// 慢:
for (int i = 0; i < n; i++)
    appendStringInfo(buf, "%s", items[i]);

// 快:
appendStringInfo(buf, "%s%s%s...", items[0], items[1], ...);

// 3. 使用最合适的追加函数
appendStringInfo(buf, "%s", str);      // 较慢
appendStringInfoString(buf, str);       // 快
appendBinaryStringInfo(buf, str, len); // 最快

7. 游标操作

7.1 游标概念

StringInfo 内置游标支持,用于顺序读取:

c
typedef struct StringInfoData
{
    char   *data;
    int     len;
    int     maxlen;
    int     cursor;  // 游标位置
} StringInfoData;

// 游标不会自动移动,需要手动管理

7.2 游标使用示例

c
// 解析逗号分隔的值
void parse_csv(const char *csv)
{
    StringInfoData buf;
    initStringInfo(&buf);
    appendStringInfoString(&buf, csv);
    
    buf.cursor = 0;
    
    while (buf.cursor < buf.len)
    {
        // 找到下一个逗号
        int start = buf.cursor;
        while (buf.cursor < buf.len && buf.data[buf.cursor] != ',')
            buf.cursor++;
            
        // 提取字段
        int field_len = buf.cursor - start;
        char *field = palloc(field_len + 1);
        memcpy(field, buf.data + start, field_len);
        field[field_len] = '\0';
        
        printf("Field: %s\n", field);
        pfree(field);
        
        // 跳过逗号
        if (buf.cursor < buf.len && buf.data[buf.cursor] == ',')
            buf.cursor++;
    }
    
    pfree(buf.data);
}

8. 高级使用模式

8.1 构建 SQL 查询

c
StringInfo
build_select_query(const char *table, List *columns, 
                   List *conditions)
{
    StringInfo buf = makeStringInfo();
    
    // SELECT
    appendStringInfoString(buf, "SELECT ");
    
    // 列名
    if (columns == NIL)
        appendStringInfoString(buf, "*");
    else
    {
        bool first = true;
        ListCell *lc;
        foreach (lc, columns)
        {
            if (!first)
                appendStringInfoString(buf, ", ");
            appendStringInfoString(buf, (const char *) lfirst(lc));
            first = false;
        }
    }
    
    // FROM
    appendStringInfo(buf, " FROM %s", table);
    
    // WHERE
    if (conditions != NIL)
    {
        appendStringInfoString(buf, " WHERE ");
        bool first = true;
        foreach (lc, conditions)
        {
            if (!first)
                appendStringInfoString(buf, " AND ");
            appendStringInfoString(buf, (const char *) lfirst(lc));
            first = false;
        }
    }
    
    return buf;
}

// 使用示例
List *cols = list_make2("id", "name");
List *conds = list_make2("id > 10", "active = true");
StringInfo query = build_select_query("users", cols, conds);
printf("%s\n", query->data);
// 输出: SELECT id, name FROM users WHERE id > 10 AND active = true
destroyStringInfo(query);

8.2 JSON 构建

c
void
append_json_pair(StringInfo buf, const char *key, const char *value)
{
    appendStringInfoChar(buf, '"');
    appendStringInfoString(buf, key);
    appendStringInfo(buf, "\": \"%s\"", value);
    appendStringInfoChar(buf, '"');
}

StringInfo
build_json_object(List *pairs)
{
    StringInfo buf = makeStringInfo();
    ListCell *lc;
    bool first = true;
    
    appendStringInfoChar(buf, '{');
    foreach (lc, pairs)
    {
        if (!first)
            appendStringInfoString(buf, ", ");
        append_json_pair(buf, pairs[lc].key, pairs[lc].value);
        first = false;
    }
    appendStringInfoChar(buf, '}');
    
    return buf;
}

8.3 SQL 标识符转义

c
// PostgreSQL 标识符需要双引号转义
void
appendQualifiedRelation(StringInfo buf, const char *schema, 
                       const char *table)
{
    if (schema != NULL && schema[0] != '\0')
    {
        appendStringInfoChar(buf, '"');
        appendBinaryStringInfo(buf, schema, strlen(schema));
        appendStringInfo(buf, "\".\"%s\"", table);
    }
    else
    {
        appendStringInfoChar(buf, '"');
        appendStringInfoString(buf, table);
        appendStringInfoChar(buf, '"');
    }
}

// 使用示例
appendQualifiedRelation(buf, "public", "users table");
// 输出: "public"."users table"

8.4 缓冲区分块处理

c
// 处理大字符串,分块输出
void
output_in_chunks(StringInfo buf, void (*output_func)(const char *, int))
{
    const char *p = buf->data;
    int remaining = buf->len;
    int chunk_size = 8192;
    
    while (remaining > 0)
    {
        int to_write = (remaining > chunk_size) ? chunk_size : remaining;
        output_func(p, to_write);
        p += to_write;
        remaining -= to_write;
    }
}

8.5 字符串累积器

c
// 模拟 StringBuffer 的用法
typedef struct
{
    StringInfoData buffer;
    int count;
} StringAccum;

void
init_string_accum(StringAccum *acc)
{
    initStringInfo(&acc->buffer);
    acc->count = 0;
}

void
accum_append(StringAccum *acc, const char *str)
{
    if (acc->count > 0)
        appendStringInfoChar(&acc->buffer, ',');
    appendStringInfoString(&acc->buffer, str);
    acc->count++;
}

char *
accum_result(StringAccum *acc)
{
    char *result = palloc(acc->buffer.len + 1);
    memcpy(result, acc->buffer.data, acc->buffer.len);
    result[acc->buffer.len] = '\0';
    return result;
}

// 使用示例
StringAccum acc;
init_string_accum(&acc);
accum_append(&acc, "apple");
accum_append(&acc, "banana");
accum_append(&acc, "cherry");
char *result = accum_result(&acc);
// result = "apple,banana,cherry"

9. 只读 StringInfo

9.1 只读模式特点

c
// 初始化只读 StringInfo
void initReadOnlyStringInfo(StringInfo str, char *data, int len);

// 只读特性:
// 1. maxlen = 0 (标记只读)
// 2. 不能调用 appendStringInfo*
// 3. 不能调用 resetStringInfo()
// 4. 调用 enlargeStringInfo 会失败

9.2 使用场景

c
// 场景 1: 包装外部缓冲区
void
process_external_buffer(char *external, int len)
{
    StringInfoData buf;
    
    // 不复制数据,直接使用外部缓冲区
    initReadOnlyStringInfo(&buf, external, len);
    
    // 只能读取 len 和 cursor
    // 不能追加
    printf("Buffer length: %d\n", buf.len);
    printf("Content: %.*s\n", buf.len, buf.data);
    
    // 函数返回,外部缓冲区由调用者负责
}

// 场景 2: 性能敏感的数据传递
void
wrap_message_data(const char *msg_data, int msg_len)
{
    StringInfoData buf;
    initReadOnlyStringInfo(&buf, (char *)msg_data, msg_len);
    
    // 快速包装,无内存复制
    parse_message(&buf);
}

9.3 只读检测

c
// 通过 maxlen 判断是否只读
bool is_readonly_string_info(StringInfo str)
{
    return str->maxlen == 0;
}

// 只读 StringInfo 不能追加
void safe_append(StringInfo str, const char *data)
{
    if (str->maxlen == 0)
    {
        // 只读,不能追加
        ereport(ERROR, 
            errmsg("cannot append to read-only StringInfo"));
    }
    appendStringInfoString(str, data);
}

10. 常见错误与调试

10.1 常见错误

c
// 错误 1: 忘记销毁 makeStringInfo 创建的 StringInfo
StringInfo leak = makeStringInfo();
appendStringInfoString(leak, "leaked");
// 忘记 destroyStringInfo(leak);  // 内存泄漏!

// 错误 2: 使用已释放的缓冲区
StringInfo buf = makeStringInfo();
appendStringInfoString(buf, "test");
char *copy = pstrdup(buf->data);
destroyStringInfo(buf);
// printf("%s\n", buf->data);  // 使用已释放内存!

// 错误 3: 混合使用 pfree 和 destroyStringInfo
StringInfoData stack_buf;
initStringInfo(&stack_buf);
// pfree(stack_buf.data);  // 错误!
// destroyStringInfo(&stack_buf);  // 错误!
pfree(stack_buf.data);  // 只释放数据缓冲区

// 正确: initStringInfo 的栈变量不需要显式销毁

10.2 调试技巧

c
// 1. 打印 StringInfo 状态
void
debug_string_info(StringInfo str, const char *name)
{
    elog(DEBUG1, "%s: len=%d, maxlen=%d, cursor=%d, data=\"%s\"",
         name, str->len, str->maxlen, str->cursor, str->data);
}

// 2. 断言检查
Assert(str->maxlen > 0);      // 不是只读
Assert(str->len < str->maxlen);  // 有空间
Assert(str->data[str->len] == '\0');  // 正确终止

// 3. Valgrind 支持
// StringInfo 使用 palloc,Valgrind 可以检测内存问题

11. pqexpbuffer 对比

PostgreSQL 还有另一个类似的数据结构 pqexpbuffer(在 src/interfaces/libpq/pqexpbuffer.h):

c
/*
 * pqexpbuffer.h 说明:
 * This module is essentially the same as the backend's StringInfo data type,
 * but is provided for use by frontend code that doesn't link the backend library.
 */
特性StringInfopqexpbuffer
使用场景后端代码前端代码
内存分配pallocmalloc
API完全相同完全相同
头文件lib/stringinfo.hlibpq/pqexpbuffer.h

注意: 两者的 API 完全兼容,可以根据链接需求选择。


12. 源码文件索引

文件路径内容描述
src/include/lib/stringinfo.hStringInfo 头文件: 结构、函数声明
src/common/stringinfo.cStringInfo 实现: 所有函数实现
src/include/libpq/pqexpbuffer.h前端版 StringInfo 头文件
src/interfaces/libpq/pqexpbuffer.c前端版 StringInfo 实现

12.1 函数速查

函数用途
makeStringInfo()创建并初始化
initStringInfo()初始化栈变量
initReadOnlyStringInfo()只读初始化
initStringInfoFromString()从现有缓冲区初始化
appendStringInfo()格式化追加
appendStringInfoString()字符串追加
appendStringInfoChar()字符追加
appendStringInfoSpaces()空格追加
appendBinaryStringInfo()二进制追加
enlargeStringInfo()预扩展缓冲区
resetStringInfo()重置内容
destroyStringInfo()销毁 (makeStringInfo 创建的)

13. 总结

13.1 核心要点

  1. 动态字符串缓冲区: 自动扩展,无需手动管理大小

  2. 四种初始化方式:

    • makeStringInfo(): 完整动态分配
    • initStringInfo(): 栈变量 + 动态缓冲区
    • initReadOnlyStringInfo(): 只读包装
    • initStringInfoFromString(): 现有缓冲区
  3. 追加函数选择:

    • appendBinaryStringInfo(): 最通用
    • appendStringInfoString(): 字符串最快
    • appendStringInfo(): 格式化最方便
    • appendStringInfoCharMacro(): 单字符最高效
  4. 内存管理:

    • 内存保持在初始化时的上下文
    • destroyStringInfo() 释放 makeStringInfo()
    • resetStringInfo() 清空内容复用缓冲区
  5. 扩展策略: 2 倍扩展,避免频繁 realloc

13.2 使用口诀

创建用 make 或 init,
追加用 append 函数,
格式化用 appendStringInfo,
字符串用 appendStringInfoString,
字符用 appendStringInfoChar,
重置用 resetStringInfo,
销毁用 destroyStringInfo。

13.3 性能提示

  • 如果知道大小,提前 enlargeStringInfo()
  • 批量追加优于多次小追加
  • 避免在循环中使用 makeStringInfo(),改用 resetStringInfo()
  • 单字符追加用宏版本 appendStringInfoCharMacro()

本文档基于 PostgreSQL 源码 (v17) 分析编写,详细阐述了 PostgreSQL StringInfoData 数据结构的设计、实现和使用方法。