string
Unsafe.Add
Unsafe.Add
是string中一个常用的方法,它不是用于向某个对象添加元素的,而是用于计算字符在内存中的偏移位置。
Split 是如何运行的
string 的 split 操作是直接进行内存操作实现的,这样可以在不创建大量新字符串副本的情况下,从原始字符串中提取子字符串。
它使用底层的内存操作来提高性能,并且会进行断言检查以确保输入参数的有效性。这有助于减少内存分配和复制操作,提高了字符串操作的效率。
private string InternalSubString(int startIndex, int length)
{
Debug.Assert(startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!");
Debug.Assert(length >= 0 && startIndex <= this.Length - length, "length is out of range!");
string result = FastAllocateString(length);
// 这一行代码的目的是计算从 _firstChar 开始偏移 startIndex 指定的字符数量后的内存位置,并返回该位置的引用。
Buffer.Memmove(
elementCount: (uint)length,
destination: ref result._firstChar,
source: ref Unsafe.Add(ref _firstChar, (nint)(uint)startIndex /* force zero-extension */));
return result;
}
Trim是如何运行的
可以看到调用分成了三个阶段:
第一阶段:判断字符串的第一个和最后一个字符是否有空格字符
第二阶段:通过 start
和 end
获取非空字符的位置
第三阶段:使用 InternalSubString
截取字符串返回
public string Trim()
{
if (Length == 0 || (!char.IsWhiteSpace(_firstChar) && !char.IsWhiteSpace(this[^1])))
{
return this;
}
return TrimWhiteSpaceHelper(TrimType.Both);
}
private string TrimWhiteSpaceHelper(TrimType trimType)
{
// end will point to the first non-trimmed character on the right.
// start will point to the first non-trimmed character on the left.
int end = Length - 1;
int start = 0;
// Trim specified characters.
if ((trimType & TrimType.Head) != 0)
{
for (start = 0; start < Length; start++)
{
if (!char.IsWhiteSpace(this[start]))
{
break;
}
}
}
if ((trimType & TrimType.Tail) != 0)
{
for (end = Length - 1; end >= start; end--)
{
if (!char.IsWhiteSpace(this[end]))
{
break;
}
}
}
return CreateTrimmedString(start, end);
}
private string CreateTrimmedString(int start, int end)
{
int len = end - start + 1;
return
len == Length ? this :
len == 0 ? Empty :
InternalSubString(start, len);
}
Replace 是如何实现的
字符匹配算法部分存放在 System.Globalization
中,这里不做分析。
private static string? ReplaceCore(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue, CompareInfo compareInfo, CompareOptions options)
{
Debug.Assert(!oldValue.IsEmpty);
Debug.Assert(compareInfo != null);
var result = new ValueStringBuilder(stackalloc char[StackallocCharBufferSizeLimit]);
result.EnsureCapacity(searchSpace.Length);
bool hasDoneAnyReplacements = false;
// 获取到第一个需要替换的字符的索引,并将该索引之前的所有内容添加到result中,再将newValue追加到result中
// 将已匹配过的 searchSpace 内容切割出去,进入下一轮循环
// 当IndexOf匹配不到字符时结束循环,将result转换为字符串返回
while (true)
{
// searchSpace:要在其中执行查找和替换操作的只读字符序列。
// oldValue:要查找并替换的目标子序列。
// newValue:用于替换目标子序列的新子序列。
// compareInfo:用于执行文化特定的比较和查找操作的 CompareInfo 对象。
// options:比较选项,用于指定查找操作的比较方式。
int index = compareInfo.IndexOf(searchSpace, oldValue, options, out int matchLength);
if (index < 0 || matchLength == 0)
{
break;
}
result.Append(searchSpace.Slice(0, index));
result.Append(newValue);
searchSpace = searchSpace.Slice(index + matchLength);
// 设置 hasDoneAnyReplacements 为 true,表示已经执行了至少一个替换
hasDoneAnyReplacements = true;
}
// 检查是否进行了替换
// 如果没有进行替换,它会释放 ValueStringBuilder 对象并返回 null,以节省内存。
if (!hasDoneAnyReplacements)
{
result.Dispose();
return null;
}
result.Append(searchSpace);
return result.ToString();
}
Remove 是如何实现的
Remove方法使用了两次内存操作将字符串的内容移动到了新的string中。
public string Remove(int startIndex, int count)
{
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
ArgumentOutOfRangeException.ThrowIfNegative(count);
int oldLength = this.Length;
ArgumentOutOfRangeException.ThrowIfGreaterThan(count, oldLength - startIndex);
if (count == 0)
return this;
int newLength = oldLength - count;
if (newLength == 0)
return Empty;
// 分配新字符串的内存
string result = FastAllocateString(newLength);
// 这行代码将从原始字符串的起始位置开始的前startIndex个字符复制到新字符串的起始位置。
// 这是保留startIndex之前字符的操作。
Buffer.Memmove(ref result._firstChar, ref _firstChar, (nuint)startIndex);
// 这行代码将从原始字符串的startIndex + count位置开始的剩余字符复制到新字符串的startIndex位置开始。
// 保留startIndex + count 之后字符的操作。
Buffer.Memmove(ref Unsafe.Add(ref result._firstChar, startIndex), ref Unsafe.Add(ref _firstChar, startIndex + count), (nuint)(newLength - startIndex));
return result;
}
Join 是如何实现的
需要注意的是,在进行 join 的时候,result只被创建了一次,所有拼接都是调用 CopyStringContent 方法进行内存操作,将 value 复制到 result 所拥有的内存上的。
private static string JoinCore(ReadOnlySpan<char> separator, ReadOnlySpan<string?> values)
{
if (values.Length <= 1)
{
return values.IsEmpty ?
Empty :
values[0] ?? Empty;
}
long totalSeparatorsLength = (long)(values.Length - 1) * separator.Length;
if (totalSeparatorsLength > int.MaxValue)
{
ThrowHelper.ThrowOutOfMemoryException_StringTooLong();
}
int totalLength = (int)totalSeparatorsLength;
// 计算新的 string 所需的总长度
foreach (string? value in values)
{
if (value != null)
{
totalLength += value.Length;
if (totalLength < 0) // Check for overflow
{
ThrowHelper.ThrowOutOfMemoryException_StringTooLong();
}
}
}
if (totalLength == 0)
{
return Empty;
}
// 根据先前获取的总长度,创建新的 string
string result = FastAllocateString(totalLength);
int copiedLength = 0;
for (int i = 0; i < values.Length; i++)
{
if (values[i] is string value)
{
int valueLen = value.Length;
if (valueLen > totalLength - copiedLength)
{
copiedLength = -1;
break;
}
// Fill in the value.
CopyStringContent(result, copiedLength, value);
copiedLength += valueLen;
}
if (i < values.Length - 1)
{
// Fill in the separator.
// Special-case length 1 to avoid additional overheads of CopyTo.
// This is common due to the char separator overload.
ref char dest = ref Unsafe.Add(ref result._firstChar, copiedLength);
if (separator.Length == 1)
{
dest = separator[0];
}
else
{
separator.CopyTo(new Span<char>(ref dest, separator.Length));
}
copiedLength += separator.Length;
}
}
// 如果复制的数量刚刚好,那么说明结果是正确的。
// 如果数量不对,说明发生了一些错误,需要重新进行拼接
return copiedLength == totalLength ?
result :
JoinCore(separator, values.ToArray().AsSpan());
}
private static void CopyStringContent(string dest, int destPos, string src)
{
Debug.Assert(dest != null);
Debug.Assert(src != null);
Debug.Assert(src.Length <= dest.Length - destPos);
Buffer.Memmove(
destination: ref Unsafe.Add(ref dest._firstChar, destPos),
source: ref src._firstChar,
elementCount: (uint)src.Length);
}
Insert 是如何实现的
接下来的基本都是老一套了,计算长度,根据长度创建 result,使用内存操作将内容分次复制到result里,返回 result
public string Insert(int startIndex, string value)
{
ArgumentNullException.ThrowIfNull(value);
ArgumentOutOfRangeException.ThrowIfGreaterThan((uint)startIndex, (uint)Length, nameof(startIndex));
int oldLength = Length;
int insertLength = value.Length;
if (oldLength == 0)
return value;
if (insertLength == 0)
return this;
// In case this computation overflows, newLength will be negative and FastAllocateString throws OutOfMemoryException
int newLength = oldLength + insertLength;
string result = FastAllocateString(newLength);
Buffer.Memmove(ref result._firstChar, ref _firstChar, (nuint)startIndex);
Buffer.Memmove(ref Unsafe.Add(ref result._firstChar, startIndex), ref value._firstChar, (nuint)insertLength);
Buffer.Memmove(ref Unsafe.Add(ref result._firstChar, startIndex + insertLength), ref Unsafe.Add(ref _firstChar, startIndex), (nuint)(oldLength - startIndex));
return result;
}
标签:string,C#,Length,int,startIndex,源码,result,ref
From: https://www.cnblogs.com/hellofriland/p/17672702.html