目前我知道的将字符串的第一个字母大写的最快方法如下:
var array = str.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new string(array);
Run Code Online (Sandbox Code Playgroud)
这涉及 2 个数组分配:char.ToUpper(array[0])以及复制到new string(array).
因为我知道array不能逃避这个方法,有没有办法使用不安全的代码来避免第二次分配?
既然您要求“最快”的方式,那么让我们对一些事情进行基准测试。毕竟,我们作为程序员喜欢使用经验证据*令人信服地点头*
老套
public string OldSchool(string value)
=> char.ToUpper(value[0]) + value[1..];
Run Code Online (Sandbox Code Playgroud)
跨度
public string TestSpan(string value)
=> string.Create(value.Length, value, (span, str) =>
{
str.AsSpan().CopyTo(span);
span[0] = char.ToUpper(span[0]);
});
Run Code Online (Sandbox Code Playgroud)
不安全
public unsafe string TestUnsafe(string value)
{
var result = new string(value);
fixed (char* p = result) p[0] = char.ToUpper(p[0]);
return result;
}
Run Code Online (Sandbox Code Playgroud)
仅限不安全的 Ascii
public unsafe string TestUnsafeAscii(string value)
{
var result = new string(value);
fixed (char* p = result)
if(p[0] >= 'a' && p[0] <= 'z') *p += (char)32;
return result;
}
Run Code Online (Sandbox Code Playgroud)
超级不安全
注意:这是针对超级勇敢者的,其中弦不内转并且突变不是问题
public unsafe string SuperUnsafe(string value)
{
fixed (char* p = value) p[0] = char.ToUpper(p[0]);
return value;
}
Run Code Online (Sandbox Code Playgroud)
配置
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.804 (2004/?/20H1)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET Core SDK=5.0.201
[Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
.NET Core 5.0 : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
Job=.NET Core 5.0 Runtime=.NET Core 5.0
Run Code Online (Sandbox Code Playgroud)
结果
| 方法 | 氮 | 意思是 | 错误 | 标准差 | 比率 | 第0代 | 第一代 | 第2代 | 已分配 |
|---|---|---|---|---|---|---|---|---|---|
| 老套 | 5 | 44.89纳秒 | 0.389纳秒 | 0.364纳秒 | 1.00 | 0.0105 | - | - | 88乙 |
| 跨度 | 5 | 26.37纳秒 | 0.170纳秒 | 0.159纳秒 | 1.00 | 0.0038 | - | - | 32乙 |
| 不安全 | 5 | 25.15纳秒 | 0.128纳秒 | 0.119纳秒 | 1.00 | 0.0038 | - | - | 32乙 |
| 不安全的Ascii | 5 | 11.92纳秒 | 0.093纳秒 | 0.073纳秒 | 1.00 | 0.0038 | - | - | 32乙 |
| 超级不安全 | 5 | 10.22纳秒 | 0.051纳秒 | 0.045纳秒 | 1.00 | - | - | - | - |
| 方法 | 氮 | 意思是 | 错误 | 标准差 | 比率 | 第0代 | 第一代 | 第2代 | 已分配 |
|---|---|---|---|---|---|---|---|---|---|
| 老套 | 10 | 49.31纳秒 | 0.595纳秒 | 0.527纳秒 | 1.00 | 0.0134 | - | - | 112乙 |
| 跨度 | 10 | 27.71纳秒 | 0.548纳秒 | 0.512纳秒 | 1.00 | 0.0057 | - | - | 48乙 |
| 不安全 | 10 | 26.76纳秒 | 0.142纳秒 | 0.126纳秒 | 1.00 | 0.0057 | - | - | 48乙 |
| 不安全的Ascii | 10 | 13.40纳秒 | 0.103纳秒 | 0.096纳秒 | 1.00 | 0.0057 | - | - | 48乙 |
| 超级不安全 | 10 | 10.28纳秒 | 0.106纳秒 | 0.094纳秒 | 1.00 | - | - | - | - |
| 方法 | 氮 | 意思是 | 错误 | 标准差 | 比率 | 第0代 | 第一代 | 第2代 | 已分配 |
|---|---|---|---|---|---|---|---|---|---|
| 老套 | 100 | 83.52纳秒 | 0.966纳秒 | 0.903纳秒 | 1.00 | 0.0564 | - | - | 472 乙 |
| 跨度 | 100 | 45.77纳秒 | 0.441纳秒 | 0.412纳秒 | 1.00 | 0.0268 | - | - | 224乙 |
| 不安全 | 100 | 44.07纳秒 | 0.511纳秒 | 0.453纳秒 | 1.00 | 0.0268 | - | - | 224乙 |
| 不安全的Ascii | 100 | 31.45纳秒 | 0.382纳秒 | 0.357纳秒 | 1.00 | 0.0268 | - | - | 224乙 |
| 超级不安全 | 100 | 10.26纳秒 | 0.078纳秒 | 0.073纳秒 | 1.00 | - | - | - | - |
| 方法 | 氮 | 意思是 | 错误 | 标准差 | 比率 | 第0代 | 第一代 | 第2代 | 已分配 |
|---|---|---|---|---|---|---|---|---|---|
| 老套 | 1000 | 512.05纳秒 | 2.909纳秒 | 2.578纳秒 | 1.00 | 0.4864 | 0.0052 | - | 4072乙 |
| 跨度 | 1000 | 260.35纳秒 | 2.593纳秒 | 2.425纳秒 | 1.00 | 0.2418 | 0.0017 | - | 2024乙 |
| 不安全 | 1000 | 255.06纳秒 | 1.587纳秒 | 1.407纳秒 | 1.00 | 0.2418 | 0.0017 | - | 2024乙 |
| 不安全的Ascii | 1000 | 247.60纳秒 | 2.500纳秒 | 2.338纳秒 | 1.00 | 0.2418 | 0.0017 | - | 2024乙 |
| 超级不安全 | 1000 | 10.21纳秒 | 0.060纳秒 | 0.056纳秒 | 1.00 | - | - | - | - |
完整测试代码
public class Test
{
private string _data;
private static readonly Random random = new Random(42);
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
[Params(5, 10, 100, 1000)] public int N;
[GlobalSetup]
public void Setup()
{
_data = RandomString(N);
}
[Benchmark]
public string OldSchool() => OldSchool(_data);
public string OldSchool(string value)
=> char.ToUpper(value[0]) + value[1..];
[Benchmark]
public string Span() => TestSpan(_data);
public string TestSpan(string value)
=> string.Create(value.Length, value, (span, str) =>
{
str.AsSpan().CopyTo(span);
span[0] = char.ToUpper(span[0]);
});
[Benchmark]
public string Unsafe() => TestUnsafe(_data);
public unsafe string TestUnsafe(string value)
{
var result = new string(value);
fixed (char* p = result) p[0] = char.ToUpper(p[0]);
return result;
}
[Benchmark]
public unsafe string SuperUnsafe() => SuperUnsafe(_data);
public unsafe string SuperUnsafe(string value)
{
fixed (char* p = value) p[0] = char.ToUpper(p[0]);
return value;
}
[Benchmark]
public string UnsafeAscii() => TestUnsafeAscii(_data);
public unsafe string TestUnsafeAscii(string value)
{
var result = new string(value);
fixed (char* p = result)
if(p[0] >= 'a' && p[0] <= 'z') *p += (char)32;
return result;
}
}
Run Code Online (Sandbox Code Playgroud)