字符串的逆序是个非常简单的算法,可以直接使用一层循环搞定,或者下面一句代码。
str = new string(str.Reverse().ToArray());
但是对于 Unicode 字符串来说,这种方法并不完全正确,因为 Unicode 里有复合字符和代理项对这两种特殊的东西。
复合字符是后跟一个或多个组合字符的基本字符,也就是说,有些符号并不是由单一的一个 char 来表示的,而是由一个基本字符后跟多个组合字符组成的。例如字符 'ë',它的 Unicode 编码是 \u00EB,但是它同样也可以使用 \u0065\u0308 来表示,其中 \u0065 对应字符 'e',\u0308 则是组合字符(表示 e 上的两点),如图 1 所示。这时候就需要用两个 char 来表示一个字符,而且它们的相对顺序不能被改变。组合字符在表示重音和数学符号时还是非常有用的。
图1 复合字符示例
代理项对是为了用 utf-16 表示 Unicode 基本多语言平面 (BMP) 以外的字符。例如符号 \U0001D160,在 C# 中是用两个 char \uD834\uDD60 表示的,其中 \uD834 是高代理项,\uDD60 是低代理项。
因此,要想更加完美的对 Unicode 字符串进行逆序,需要保证复合字符和代理项对的顺序。还好 C# 提供了 TextElementEnumerator 类来枚举字符串的文本元素,这样就不需要自己去考虑 Unicode 的具体编码方式了。
具体的实现还是一次循环,对于普通字符还是直接对 char 进行逆序,仅当遇到复合字符或代理项对时,才使用 TextElementEnumerator 进行枚举,并以文本元素为单位进行逆序。我了解有 TextElementEnumerator 这个类,也是当初在看 Microsoft.VisualBasic.Strings.StrReverse 方法的源代码才发现的,微软自己的类库考虑的的确比较全面。
using System.Globalization;
namespace Cyjb {
/// <summary>
/// 提供 <see cref="System.String"/> 类的扩展方法。
/// </summary>
public static class StringExt {
/// <summary>
/// 返回指定字符串的字符顺序是相反的字符串。
/// </summary>
/// <param name="str">字符反转的字符串。</param>
/// <returns>字符反转后的字符串。</returns>
/// <remarks>参考了 Microsoft.VisualBasic.Strings.StrReverse 方法的实现。</remarks>
public static string Reverse(this string str) {
if (string.IsNullOrEmpty(str)) {
return string.Empty;
}
int len = str.Length;
int end = len - 1;
int i = 0;
char[] strArr = new char[len];
while (end >= 0) {
switch (char.GetUnicodeCategory(str[i])) {
case UnicodeCategory.Surrogate:
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.EnclosingMark:
// 字符串中包含组合字符,翻转时需要保证组合字符的顺序。
// 为了能够包含基字符,回退一位。
if (i > 0) {
i--;
end++;
}
TextElementEnumerator textElementEnumerator = StringInfo.GetTextElementEnumerator(str, i);
textElementEnumerator.MoveNext();
int idx = textElementEnumerator.ElementIndex;
while (end >= 0) {
i = idx;
if (textElementEnumerator.MoveNext()) {
idx = textElementEnumerator.ElementIndex;
} else {
idx = len;
}
for (int j = idx - 1; j >= i; strArr[end--] = str[j--]) ;
}
goto EndReverse;
}
// 直接复制。
strArr[end--] = str[i++];
}
EndReverse:
return new string(strArr);
}
}
}
关于 Unicode 的更多资料,可以参考《循序渐进全球化:支持 Unicode》。以上的字符串逆序也并不一定是完美的解决方案,不过条件所限,只能这样了。
代码可见 Cyjb.StringExt 类中的 Reverse 方法。