ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

字符串核心知识盘点:转换、比较与性能避坑指南

2026/9/11 16:15:05 拓冰建站 浏览量
字符串核心知识盘点:转换、比较与性能避坑指南 字符串这东西写过几年代码的人估计没人敢拍胸脯说自己完全搞懂了。我翻自己的技术笔记“字符串补充知识点”这个标题下已经积累了十几条内容从字符串转数字、字符串逆序、Java 里 SetString contains 判断到 Oracle 里用查不出数据、SQL Server 里字符串转 int再到各种语言里数组转字符串、JSON 字符串反序列化每一条都对应过线上真实问题。今天把这些内容整理成一篇完整的笔记按“基础认知、转换、常用操作、比较判断、排序与性能”五个方向重新梳理一遍适合所有写 Java、Python、C、C# 或者天天要跟 SQL 打交道的人尤其是刚工作没几年的朋友这里面的坑你大概率会踩一遍。1. 字符串基础存储、不可变性与 length 的坑1.1 字符串到底是对象还是字符数组不同语言对字符串的定位差异很大。Java、Python、C#、JavaScript 里的字符串都是不可变对象String 类型一旦创建内部字符序列就不会再变。C 语言里字符串是字符数组C 里的 std::string 是封装了动态字符缓冲区的类。这一点会直接影响很多操作。比如 Java 的abc.substring(0, 2)返回一个新对象修改返回值不会影响原字符串但 C 语言里直接操作char[]你改的是同一块内存。C 语言的char *p hello和char arr[] hello看起来差不多实际上前者指向只读常量区后者在栈上分配可修改的副本。很多 C 语言初学者在 PTA 上写字符串逆序题时用char *p abc; p[0] x;直接崩溃就是因为没搞清楚这一点。字符串的不可变性还带来一个隐藏问题频繁拼接会产生大量临时对象。Java 里循环里写s a每次循环都新创建一个字符串再丢掉旧的GC 压力很大。正确做法是使用 StringBuilderPython 里则应该用.join(list)C# 里用 StringBuilderC 语言里用strncat或手动维护指针。这些细节看起来不起眼但数据量一上来性能差异是肉眼可见的。1.2 字符串长度length() 返回的不一定是“字符个数”这是我最想单独拎出来讲的一个基础问题。Java 的String.length()返回的是 UTF-16 编码下的 code unit 数量而不是我们自然语言里理解的“字符”。遇到 emoji 或者生僻字一个“字”可能占两个 code unit。比如.length()在 Java 里是 2JavaScript 里也是 2。Python 的len()是 1因为 Python 3 的字符串按 Unicode code point 计数。SQL Server 的LEN()会忽略末尾空格你在排查字符串长度时容易产生“明明有空格为什么 len 一样”的困惑。这些差异直接影响子串截取和下标的判断。Java 里substring(0, 1)截一个 emoji 会得到半个字符显示成乱码。C 语言里碰到中文或 UTF-8 编码时strlen返回的是字节数一个汉字占 3 个字节按字节遍历再截断极容易把字符切坏。所以写通用工具时判断字符串长度不能简单用length()要看业务场景里说的“长度”到底是字符数还是字节数有没有包含尾随空格。2. 转换专题数字、数组、列表、JSON、十六进制与枚举2.1 字符串转数字Java、Python、SQL Server 的典型差异字符串转数字是日常开发里最高频的操作之一但每个语言踩坑的地方不一样。Java 里最常见的是Integer.parseInt(123)和Integer.valueOf(123)两者都能转前者返回 int后者返回 Integer。要注意的是输入字符串包含空格、正负号之外的非数字字符直接抛NumberFormatException。处理用户输入时建议先做 trim再用正则或异常捕获兜底。SQL Server 里字符串转数字用CAST(123 AS INT)或者CONVERT(INT, 123)但如果字段里混入非数字内容整个转换直接报错。SQL Server 2012 之后有TRY_CAST和TRY_CONVERT转换失败返回 NULL不会让整个查询崩掉。我建议数据清洗场景优先用 TRY_CAST能省掉很多麻烦。Python 相对省心int(123)直接转但int(12.3)会报错Decimal 或者 float 需要单独处理。还有一个点int(0x1A, 0)可以自动识别进制但如果你忘写第二个参数默认十进制0x1A会被当成非法输入。转十六进制值时用int(ff, 16)是最稳妥的写法。2.2 数组、List 和字符串互转ListMap 的坑“数组转字符串”在每个语言里都有标准做法。Java 里数组转字符串可以这样做String[] arr {a, b, c}; String joined String.join(,, arr); ListString list Arrays.asList(arr); String joined2 list.stream().collect(Collectors.joining(,));这里的Collectors.joining()可以指定分隔符、前缀和后缀生成类似[a, b, c]的效果。C 里一般用std::ostringstream配合循环拼接或 C20 之后用std::ranges但最朴素的方式依然最不容易出错。C 语言里要把字符数组转成字符串其实就是保证数组末尾补\0char buf[8] {a,b,c,0};就能直接当字符串用。热搜里有一条“listmap字符串 转list”我估计实际场景是把 JSON 字符串反序列化成ListMapString, Object。用 Jackson 是这样写的ObjectMapper mapper new ObjectMapper(); ListMapString, Object list mapper.readValue(jsonStr, new TypeReferenceListMapString, Object() {});有个隐藏问题MapString, Object里的Object反序列化出来可能是LinkedHashMap、Integer、String等拿到 value 后直接强转字符串很可能失败。稳妥做法是用String.valueOf(map.get(key))再加一次判空或者干脆定义一个 DTO 类让 Jackson 帮你做类型转换。2.3 枚举类型与字符串互转枚举和字符串互转在业务代码里出现频率很高。Java 里内置了name()和valueOf(String)但这两个方法都基于枚举字段名一旦代码混淆或字段改名序列化出来的字符串就变了。更推荐的做法是给枚举加一个code字段然后用 code 对应public enum Status { SUCCESS(1), FAIL(0); private final String code; Status(String code) { this.code code; } public String getCode() { return code; } public static Status fromCode(String code) { for (Status s : values()) { if (s.code.equals(code)) return s; } throw new IllegalArgumentException(unknown code); } }C# 里直接Enum.ToString()和Enum.ParseT()Python 里MyEnum(value)或MyEnum[NAME]都行C 语言没有原生枚举转字符串常见做法是写数组映射索引就是枚举值。C 更麻烦要么手写switch要么借助宏或者c20的反射支持但工程上最稳的还是维护一张查找表。2.4 JSON、十六进制、Base64字符串可以“装下”任何东西Java 字符串转 JSON 用 Jackson 时有个经典配置错误字段名不匹配会导致对象属性全是 null但不会抛异常。排查时可以先开启FAIL_ON_UNKNOWN_PROPERTIES让多出来的字段直接报错方便定位。写操作时用objectMapper.writeValueAsString(obj)中文不会乱码因为 Jackson 默认输出就是 UTF-8。Python 里十六进制和字符串互转常用两种方式# 字符串 - 十六进制 s hello hex_str s.encode(utf-8).hex() # 68656c6c6f # 十六进制 - 字符串 bytes.fromhex(68656c6c6f).decode(utf-8) # hello注意一定要指定编码默认 UTF-8 没问题但如果原始字符串包含中文转出来的是e4bda0这类长串再转回来也必须用同样的编码否则一定乱码。Base64 本质上也是类似思路把二进制或者字符串变成可见字符常用于 URL 参数传递Java 里用Base64.getUrlEncoder()Python 里用base64.urlsafe_b64encode()比普通 Base64 更安全因为包含 /的字符串放到 URL 里会被转义。3. 字符串常用操作截取、拼接、逆序、替换、大小写那些细节3.1 截取与插入分清“区间”还是“长度”字符串截取我敢说绝大多数人在 C# 和 Java 之间切换时都会懵一下。Java 的substring(0, 3)是从下标 0 取到下标 3不包含 3截取结果是 3 个字符。C# 的Substring(0, 3)是从下标 0 开始取 3 个字符结果也是 3 个字符但第二个参数是长度。如果你把 C# 的习惯带到 Javasubstring(1, 2)本意想取两个字符实际只取到一个。Python 的切片s[1:3]和 Java 类似也是左闭右开但下标支持负数s[-1]可以直接取最后一个字符方便很多。字符串插入指定字符的场景也很常见。Java 里用StringBuilder.insert(index, x)Python 没有内置 insert 方法但可以切片拼接s abcd # 在下标 2 的位置插入 X得到 abXcd s s[:2] X s[2:]如果是批量插入比如每 4 位隔一个逗号最简单的思路是从后往前插入避免破坏未处理部分的索引。这个方法我在格式化银行卡号、身份证号时用过很多次比一遍遍重算 index 稳得多。3.2 字符串拼接哪些写法会拖垮性能字符串拼接看起来太简单了但性能差异可以到几十倍。Java 里单行a b c编译器会自动优化成 StringBuilder但在循环里写s item每轮循环都新建 StringBuilder性能就很差。正确写法是循环外建一个 StringBuilder循环内 append。Python 里a b c没有编译器优化循环里一直时间复杂度接近 O(n^2)正确做法是收集到列表再用.join(list)。C 语言的字符串拼接是另一个极端。strcat(dest, src)需要保证 dest 有足够空间否则缓冲区溢出。即使空间够频繁strcat每次都要从头找字符串末尾性能也很差。更好的办法是使用snprintfchar buf[1024]; int offset 0; offset snprintf(buf offset, sizeof(buf) - offset, %s, hello); offset snprintf(buf offset, sizeof(buf) - offset, %s, world);这段写法把写入位置随 offset 移动避免了 strcat 每次从头扫。在嵌入式或者底层 C 项目里我基本都这么写。3.3 字符串逆序从 C 语言 PTA 题说起搜索词里反复出现“字符串逆序 c 语言 pta”“字符串逆序输出 c”说明这是很多人刚学 C 语言时必经的一道题。最经典的双指针写法#include stdio.h #include string.h void reverse(char s[]) { int left 0, right strlen(s) - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right--] tmp; } }前提是s是可修改的字符数组不能是字符串常量指针。这个题的另一个坑是输入可能带空格直接scanf(%s)会中断正确做法是用fgets读取一整行再去掉末尾换行符。现代语言就简单多了Pythons[::-1]Cstd::reverse(s.begin(), s.end())Javanew StringBuilder(s).reverse().toString()C#new string(s.Reverse().ToArray())。逆序还有一个常见变形是判断回文串题目描述里专门给了定义从前往后读和从后往前读一样。用双指针从两端向中间扫描遇到非字母数字的先跳过最后比较时统一转成小写。这个解法同时考验你对“逆序”和“比较”两个知识点的理解。3.4 字符串替换与大小写正则和 Locale 才是隐藏难点Java 开发者最容易踩的替换坑是replace和replaceAll的区别。replace(CharSequence, CharSequence)所有字符都是字面量replaceAll(String, String)第一个参数是正则表达式。如果你想把字符串里的$name替换成变量用replaceAll($name, x)会报错因为$在正则里是特殊字符即使用replaceAll(\\$name, x)第二个参数里的$还有特殊含义又需要额外转义。所以能用replace就不要用replaceAll只有在真正需要正则时才用后者。大小写转换的坑更隐蔽。Java 的String.toUpperCase()依赖默认 Locale在土耳其语环境下i.toUpperCase()会变成İ而不是I。做字符串比较或规范化时要么显式传Locale.ROOT要么用toUpperCase(Locale.ENGLISH)。Python 里也有类似问题但相对少见。C# 里字符串比较推荐用StringComparison.OrdinalIgnoreCase不要用ToLower()之后比较后者会额外分配内存还可能出现意想不到的规则差异。4. 比较与判断contains、NULL、回文、同构与 SQL 查询4.1 Java SetString 的 contains比的不是“看起来一样”“java setstring 是否包含某个字符串”这个问题很多人第一反应是set.contains(str)但没想清楚背后的机制。HashSet 先算 hashCode 定位到桶再用 equals 比较同一个桶里的元素。所以abc.equals(abc)返回 true前提是两个字符串内容完全相同包括大小写。HashSet默认是区分大小写的如果你要忽略大小写判断不能用普通HashSetString可以用TreeSetString(String.CASE_INSENSITIVE_ORDER)或者干脆把输入统一转成小写再放进去。还有一个实际开发中常见的问题SetString里存的是对象引用还是字面量如果从数据库或文件读取到的字符串即使内容相同引用也不同但 equals 比较内容所以 contains 没问题。但如果对象本身没有正确重写 equals 和 hashCodecontains 就失效了。字符串类本身没问题问题通常出在你自己定义的包装类上。4.2 Oracle 值为空时为什么用查不出来“oracle 的值为空用某字符串查不出来”是数据库开发里特别经典的一个坑。Oracle 把空字符串当作 NULL 处理NULL 参与比较时结果不是 true 也不是 false而是 UNKNOWN。WHERE name 张三只返回那些 name 不等于“张三”的行name 为 NULL 的行会被直接过滤掉。解决方法很明确要么显式处理 NULLSELECT * FROM users WHERE name 张三 OR name IS NULL;要么用 NVL 或 COALESCE 把 NULL 转成默认值SELECT * FROM users WHERE NVL(name, ) 张三;但是如果业务里空字符串和 NULL 有不同含义第二种写法会把“空字符串”和“NULL”混在一起需要谨慎。SQL Server 和 Oracle 不太一样SQL Server 里空字符串不会自动变 NULL但LEN()会忽略末尾空格所以你在 SQL Server 里遇到“字符串看起来有空格但没有长度”的情况十有八九是LEN和DATALENGTH的区别没搞清楚。SQL Server 的字符串比较默认忽略尾随空格abc abc 会返回 true这条规则和 Oracle 的 NULL 坑并列都是新手最常踩的数据库字符串问题。4.3 前端字符串包含判断JS 的 includes、indexOf 与正则JavaScript 判断字符串是否包含ES6 之后首选str.includes(xxx)返回布尔值语义直白。indexOf(xxx) 0是老式写法功能完全一样但需要多写一个比较。前端同学容易忽略两个点一是大小写Hello.includes(hello)是 false二是includes匹配的是连续字符不是子序列。如果只判断有没有包含用 includes 就够了复杂模式匹配再用正则比如/abc/i.test(str)。C# 里string.Contains(xxx)默认区分大小写忽略大小写可以传StringComparison.OrdinalIgnoreCase。Java 的String.contains没有忽略大小写重载通常用str.toLowerCase().contains(target.toLowerCase())或者Pattern.compile注意toLowerCase()的 Locale 问题见上文。4.4 回文串与同构字符串两类典型的“字符串比较”题回文串的判断我刚才已经提到关键是双指针从两端向中间遇到无效字符跳过。同构字符串是另一类常见面试题比如egg和add是同构的因为e-ag-d是一对一映射foo和bar不是同构因为o同时映射了a和r。解法是维护两个 Map分别记录s - t和t - s的映射每次遍历时校验是否冲突。这一个算法题背后其实是“字符串比较”的问题核心不是比较整个字符串而是比较字符间的映射关系。还有一类搜索词叫“小明和字符串”这类算法题通常围绕字符串的排列、统计字母频率或者子串匹配展开。比如给定一串字符串统计某个字符出现几次或者给定一个模式串找匹配的子串。遇到这种题先明确是“比较排列”还是“比较内容”。比较两个字符串是否由同一组字符排列而成最直接的办法是排序后逐位比较或者用长度为 26 的计数数组统计次数后者的时间复杂度更低也不容易受排序本身的影响。“字符串比较排列”这个词说的就是这个场景。5. 排序、模板字符串、压缩优化与性能细节5.1 字符串排序字典序、长度、自然排序三种需求字符串排序不能一概而论。默认的Arrays.sort(strs)或Collections.sort(list)按字典序也就是每一位字符的 ASCII/Unicode 码点进行比较。这个方法对纯英文字符串没问题但遇到数字字符串就很反直觉10会排在2前面因为1的 ASCII 码小于2。如果需要“自然排序”即在数字部分按数值大小排序就不能直接 compareTo。Java 里自定义比较器Arrays.sort(strs, Comparator.comparingInt(String::length));如果要按数值字符串大小排序可以转成数字再比但注意转换失败和溢出问题。Python 里同样sorted([10, 2])结果是[10, 2]想按数值排就得keyint。C 里用std::sort(vec.begin(), vec.end())也是字典序想按自然排序可以写自定义比较函数。“字符串比较排列”这里还有一个细节稳定排序 vs 不稳定排序。Java 的Collections.sort对对象是稳定排序基本类型数组Arrays.sort(int[])使用的是双轴快速排序不稳定但如果数组是String[]用的是 TimSort 的稳定版本。这个差异在按多个字段排序时会影响结果。5.2 多行字符串和模板字符串不同语言的进化“java 字符串多行写法”和“模板字符串”是很多 Java 开发者会主动搜索的问题。Java 15 才正式推出文本块用三个双引号包裹String sql SELECT * FROM users WHERE name ? ;Java 15 之前写多行 SQL 要么用拼接要么用StringBuilder可读性很差。Java 的文本块会自动处理缩进和换行但\转义依然存在想拼特殊字符要小心。JavaScript 的模板字符串是 ES6 就有的能力const name abc; const msg Hello ${name};重点是支持变量插值和换行非常方便。Python 的三引号和 f-string 也是类似能力C# 11 推出的原始字符串字面量用三个双引号和 Java 文本块思路一致。多行字符串看着是小事但在写 SQL、JSON 模板、日志模板时能明显提升代码可读性也减少了转义错误。5.3 字符串资源、加密压缩与体积优化“字符串加密压缩体积”这个热搜词我猜是有人在处理配置文本或网络传输时想优化体积。要区分几件事常见 MD5、SHA-256 是哈希摘要不是加密也不能还原Base64 是编码不是加密只是让二进制数据变成可打印字符真正加密需要 AES、RSA 这类算法。如果你只是不想让人一眼看到字符串明文Base64 加一层混淆可以满足低强度需求但不适合敏感数据。压缩体积的思路和加密不同。对一段很长的 JSON 或文本先用DeflaterJava或zlibPython压缩再转 Base64体积通常会明显下降。但短字符串压缩后反而可能变大因为压缩算法自带头部。我在做接口传输大文本时一般先判断字符串长度超过阈值才压缩否则没必要。还有一个无关但常见的点从网上或其他项目下载字符串资源文件时先确认文件编码尤其是.txt、.json、.properties文件编码和换行符不同直接读取很容易乱码。5.4 小字符串优化、指针数组与 Python 查 Excel“sso 小字符串优化”是 C 里 std::string 的一个实现细节。短字符串不分配堆内存而是直接存在 string 对象内部的缓冲区里这就是 Small String Optimization。所以在 C 中std::string对象本身比char*大很多但创建和复制短字符串时性能很好因为避免了堆分配。理解了这一点你就不太会在项目里为了省内存过度地把std::string改成原始char*除非你做了性能分析确认 block 就在字符串存储上。C 语言里“指针数组存放字符串”则正好相反char *names[] {a, b};数组里每个元素都是指针指向字符串常量所以适合存放长度不固定的字符串集合。但如果字符串内容后续需要修改就要用二维字符数组char names[3][20]每行固定最大长度。选择哪种取决于你怎么使用这些字符串是只读遍历还是需要修改。顺带提一个非常实用的小场景用 Python 在 Excel 里查找字符串。很多人第一反应是用 pandas其实如果只是简单查找openpyxl 足够from openpyxl import load_workbook wb load_workbook(data.xlsx) ws wb.active for row in ws.iter_rows(): for cell in row: if isinstance(cell.value, str) and 目标字符串 in cell.value: print(cell.coordinate, cell.value)读取大 Excel 时openpyxl 默认read_onlyTrue能显著减少内存占用。另外 Excel 单元格里的字符串可能带有不可见字符或多余空格查找前统一strip()是应该养成的习惯。这里的核心思路还是回到字符串比较先清洗再判断避免因为不可见字符导致明明看到了内容却搜不出来。整理到这里我最大的感受是字符串相关的坑80% 出现在“边界情况”空串与 NULL、大小写规则、编码不一致、比较对象的底层实现差异。每次踩坑花两分钟把原因记到笔记里会比在网上一次次搜同样的问题高效很多。最后再分享一个小习惯凡是涉及字符串转数字、JSON 序列化、日期格式化的代码我都会强制要求写单元测试尤其是包含空字符串、特殊符号和超长字符串的用例。这个习惯帮我挡下了很多次线上事故也让我这份字符串笔记越来越厚实。