我刚刚在c# 2.0中写了一个字符串反向函数(即LINQ不可用),然后想到了这个:
public string Reverse(string text)
{
char[] cArray = text.ToCharArray();
string reverse = String.Empty;
for (int i = cArray.Length - 1; i > -1; i--)
{
reverse += cArray[i];
}
return reverse;
}
就我个人而言,我并不喜欢这个功能,我相信有更好的方法来实现它。是吗?
首先,你必须理解的是str+=将调整字符串内存大小,为1个额外的字符腾出空间。这很好,但是如果你有一本1000页的书,你想要反转,这将需要很长时间来执行。
有些人建议的解决方案是使用StringBuilder。字符串构建器在执行+=时所做的是分配更大的内存块来保存新字符,这样它就不需要在每次添加字符时进行重新分配。
如果你真的想要一个快速和最小的解决方案,我建议如下:
char[] chars = new char[str.Length];
for (int i = str.Length - 1, j = 0; i >= 0; --i, ++j)
{
chars[j] = str[i];
}
str = new String(chars);
在这个解决方案中,在初始化char[]时有一个初始内存分配,在string构造函数从char数组构建字符串时有一个初始内存分配。
在我的系统上,我为您运行了一个测试,反转了一个2750,000个字符的字符串。以下是10次执行的结果:
StringBuilder: 190K - 200K tick
字符数组:130K - 160K
我还运行了一个正常String +=的测试,但我在10分钟后放弃了它,没有输出。
但是,我也注意到,对于较小的字符串,StringBuilder更快,因此您必须根据输入来决定实现。
干杯
public string Reverse(string input)
{
char[] output = new char[input.Length];
int forwards = 0;
int backwards = input.Length - 1;
do
{
output[forwards] = input[backwards];
output[backwards] = input[forwards];
}while(++forwards <= --backwards);
return new String(output);
}
public string DotNetReverse(string input)
{
char[] toReverse = input.ToCharArray();
Array.Reverse(toReverse);
return new String(toReverse);
}
public string NaiveReverse(string input)
{
char[] outputArray = new char[input.Length];
for (int i = 0; i < input.Length; i++)
{
outputArray[i] = input[input.Length - 1 - i];
}
return new String(outputArray);
}
public string RecursiveReverse(string input)
{
return RecursiveReverseHelper(input, 0, input.Length - 1);
}
public string RecursiveReverseHelper(string input, int startIndex , int endIndex)
{
if (startIndex == endIndex)
{
return "" + input[startIndex];
}
if (endIndex - startIndex == 1)
{
return "" + input[endIndex] + input[startIndex];
}
return input[endIndex] + RecursiveReverseHelper(input, startIndex + 1, endIndex - 1) + input[startIndex];
}
void Main()
{
int[] sizes = new int[] { 10, 100, 1000, 10000 };
for(int sizeIndex = 0; sizeIndex < sizes.Length; sizeIndex++)
{
string holaMundo = "";
for(int i = 0; i < sizes[sizeIndex]; i+= 5)
{
holaMundo += "ABCDE";
}
string.Format("\n**** For size: {0} ****\n", sizes[sizeIndex]).Dump();
string odnuMaloh = DotNetReverse(holaMundo);
var stopWatch = Stopwatch.StartNew();
string result = NaiveReverse(holaMundo);
("Naive Ticks: " + stopWatch.ElapsedTicks).Dump();
stopWatch.Restart();
result = Reverse(holaMundo);
("Efficient linear Ticks: " + stopWatch.ElapsedTicks).Dump();
stopWatch.Restart();
result = RecursiveReverse(holaMundo);
("Recursive Ticks: " + stopWatch.ElapsedTicks).Dump();
stopWatch.Restart();
result = DotNetReverse(holaMundo);
("DotNet Reverse Ticks: " + stopWatch.ElapsedTicks).Dump();
}
}
输出
尺寸:10
Naive Ticks: 1
Efficient linear Ticks: 0
Recursive Ticks: 2
DotNet Reverse Ticks: 1
尺寸:100
Naive Ticks: 2
Efficient linear Ticks: 1
Recursive Ticks: 12
DotNet Reverse Ticks: 1
规格:1000
Naive Ticks: 5
Efficient linear Ticks: 2
Recursive Ticks: 358
DotNet Reverse Ticks: 9
尺寸:10000
Naive Ticks: 32
Efficient linear Ticks: 28
Recursive Ticks: 84808
DotNet Reverse Ticks: 33
因为我喜欢两个答案-一个是使用字符串。创建,因此高性能和低分配和另一个正确性-使用StringInfo类,我决定需要一种组合方法。这是最终的字符串反转方法:)
private static string ReverseString(string str)
{
return string.Create(str.Length, str, (chars, state) =>
{
var enumerator = StringInfo.GetTextElementEnumerator(state);
var position = state.Length;
while (enumerator.MoveNext())
{
var cluster = ((string)enumerator.Current).AsSpan();
cluster.CopyTo(chars.Slice(position - cluster.Length));
position -= cluster.Length;
}
});
}
还有一种更好的方法,使用StringInfo类的方法,它通过只返回索引来跳过Enumerator的大量字符串分配。
private static string ReverseString(string str)
{
return string.Create(str.Length, str, (chars, state) =>
{
var position = 0;
var indexes = StringInfo.ParseCombiningCharacters(state); // skips string creation
var stateSpan = state.AsSpan();
for (int len = indexes.Length, i = len - 1; i >= 0; i--)
{
var index = indexes[i];
var spanLength = i == len - 1 ? state.Length - index : indexes[i + 1] - index;
stateSpan.Slice(index, spanLength).CopyTo(chars.Slice(position));
position += spanLength;
}
});
}
与LINQ解决方案相比的一些基准测试:
String length 20:
LINQ Mean: 2,355.5 ns Allocated: 1440 B
string.Create Mean: 851.0 ns Allocated: 720 B
string.Create with indexes Mean: 466.4 ns Allocated: 168 B
String length 450:
LINQ Mean: 34.33 us Allocated: 22.98 KB
string.Create Mean: 19.13 us Allocated: 14.98 KB
string.Create with indexes Mean: 10.32 us Allocated: 2.69 KB