正确做法是先用 math.max(0, str.length - n) 计算起始位置再调用 substring;若需严格截取则加长度判断;处理 unicode 字符应使用 stringinfo;注意 null 和空字符串防护,避免 argumentoutofrangeexception。
用 substring 截最后 n 位,但别硬算索引
直接写 str.substring(str.length - n) 看似简单,实际一跑就抛 argumentoutofrangeexception——字符串比你要截的位数还短时,length - n 就是负数。
正确做法是先保底:用 math.max(0, str.length - n) 算起始位置,再传给 substring。
- 如果
n = 5,而str = "ab",Math.Max(0, 2 - 5) → 0,结果就是整个"ab",不会崩 - 想严格只取最后 5 位、不够就返回空?那就得加判断:
str.Length >= n ? str.Substring(str.Length - n) : string.Empty -
Substring不支持负索引(不像 Python 的s[-n:]),这点必须心里有数
替代方案:Span<char>.Slice</char> 更快,但要注意生命周期
.NET Core 2.1+ 可用 Span 避免字符串分配,尤其在高频循环里明显。写法是 str.AsSpan().Slice(Math.Max(0, str.Length - n)),返回的是 ReadOnlySpan<char></char>。
- 不能直接赋给
string变量,要转成字符串得调.ToString() -
Span绑定原字符串内存,不能跨 async 方法或 return 出作用域,否则可能读到垃圾数据 - 纯计算场景(比如日志脱敏只取末4位卡号)用
Span省 GC,但要拼接或持久化,老实用Substring
遇到中文、emoji 时,Substring 按字节还是字符?
Substring 操作的是 char(UTF-16 code unit),不是 Unicode 字符。一个 emoji 如 "??" 占两个 char,用 Substring(str.Length - 2) 可能切在中间,输出乱码 。
- 真要按“人眼看到的字符”截取,得用
StringInfo:StringInfo.GetTextElementEnumerator(str).Cast<textelement>().TakeLast(n).Aggregate((a,b) => a+b)</textelement> - 但性能差很多,日常 ID、数字、英文字段完全不用考虑;只有处理用户昵称、评论等不可控输入时才需兜底
- 更轻量的折中:用
Regex.Replace(str, @"^.{" + (str.Length - n) + "}", ""),但正则也有开销,别滥用
别忽略空值和 null,Substring 不帮你判
null.Substring(...) 直接 NullReferenceException,空字符串 "" 虽然不崩,但 "".Substring(0) 返回空,"".Substring(1) 崩——行为不一致。
- 统一前置防御:
string.IsNullOrEmpty(str) ? string.Empty : str.Substring(...) - 如果项目启用了 nullable reference types,声明变量时加
?并配!.或空合并操作符更稳妥:str?.Substring(Math.Max(0, str.Length - n)) ?? string.Empty - 别依赖 try-catch 捕获
ArgumentOutOfRangeException来兜底,这属于设计缺陷,不是容错
事情说清了就结束。最常踩的坑其实是把 Substring 当成安全函数用,其实它连长度都不校验——你喂它什么索引,它就信什么。











