字符串.Substring与string.take

本文关键字:take string Substring 字符串 | 更新日期: 2023-09-27 18:03:45

如果您只想取字符串的一部分,则主要使用substring方法。这有一个缺点,必须先测试字符串的长度以避免错误。例如,您希望将数据保存到数据库中,并希望将值截断为前20个字符。

如果执行temp.substring(0,20),但temp只包含10个字符,则抛出异常。

我认为有两种解决方案:

  1. 测试长度,如果需要,执行子字符串
  2. 使用扩展方法

        string temp = "1234567890";
        var data= new string( temp.Take(20).ToArray());
        --> data now holds "1234657890"
    

当使用Take方法时,在速度或内存使用方面是否有任何缺点?这样做的好处是您不必编写所有这些if语句。

字符串.Substring与string.take

如果您发现自己经常这样做,为什么不编写一个扩展方法呢?

例如:

using System;
namespace Demo
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("123456789".Left(5));
            Console.WriteLine("123456789".Left(15));
        }
    }
    public static class StringExt
    {
        public static string Left(this string @this, int count)
        {
            if (@this.Length <= count)
            {
                return @this;
            }
            else
            {
                return @this.Substring(0, count);
            }
        }
    }
}

正如Henk Holtermand所说,Take()创建IEnumerator,然后您需要ToArray()调用。

因此,如果性能在您的应用程序中很重要,或者您将在进程中多次执行子字符串,则性能可能会出现问题。

我写了一个示例程序来测试Take()方法到底有多慢,结果如下:

测试了一千万次:

  • 执行子串时间:266 ms
  • 取操作执行时间:1437 ms

代码如下:

    internal const int RETRIES = 10000000;
    static void Main(string[] args)
    {
        string testString = Guid.NewGuid().ToString();
        long timeSubstring = MeasureSubstring(testString);
        long timeTake = MeasureTake(testString);
        Console.WriteLine("Time substring: {0} ms, Time take: {1} ms",
            timeSubstring, timeTake);
    }
    private static long MeasureSubstring(string test)
    {
        long ini = Environment.TickCount;
        for (int i = 0; i < RETRIES; i++)
        {
            if (test.Length > 4)
            {
                string tmp = test.Substring(4);
            }
        }
        return Environment.TickCount - ini;
    }
    private static long MeasureTake(string test)
    {
        long ini = Environment.TickCount;
        for (int i = 0; i < RETRIES; i++)
        {
            var data = new string(test.Take(4).ToArray());
        }
        return Environment.TickCount - ini;
    }

首先我不想回答(因为已经有有效的答案),但我想添加一些不适合作为评论的东西:

你说的是性能/内存问题。正确的。正如其他人所说,string.SubString更高效,因为它是如何内部优化的,因为LINQ是如何与string.Take()一起工作的(字符枚举…等)。

没有人说的是Take()在您的例子中的主要缺点是它完全破坏了子字符串的简单性。正如Tim所说,要获得您想要的实际字符串,您必须这样写:
string myString = new string(temp.Take(20).ToArray());

该死的……这比(参见Matthew的扩展方法):

更难理解。
string myString = temp.Left(20);

LINQ对于很多用例都很好,但如果没有必要就不应该使用。即使是一个简单的循环有时也比LINQ更好(即更快,更可读/可理解),所以想象一个简单的子字符串…

在你的案例中总结LINQ:

    表现糟糕
  • 少读
  • 少可以理解
  • 需要LINQ(因此不能与。net 2.0一起使用)

@Daniel的答案的变体,在我看来更准确。
Guid的长度是36。我们正在创建一个字符串长度从1到36不等的列表,我们的目标是使用substring/take方法获取18个字符串,因此大约有一半将通过。

我得到的结果表明,TakeSubstring慢6-10倍

结果示例:

Build time: 3812 ms
Time substring: 391 ms, Time take: 1828 ms
Build time: 4172 ms
Time substring: 406 ms, Time take: 2141 ms
因此,对于 500万个字符串,执行大约 250万次操作,总时间为2.1秒,或者大约0.0008564毫秒= ~ 1微秒每次操作。如果你觉得你需要为substring缩短5,那就去做吧,但我怀疑在现实生活中,在tights循环之外,你会感觉到区别。
void Main()
{
    Console.WriteLine("Build time: {0} ms", BuildInput());
    Console.WriteLine("Time substring: {0} ms, Time take: {1} ms", MeasureSubstring(), MeasureTake());
}
internal const int RETRIES = 5000000;
static internal List<string> input;
// Measure substring time
private static long MeasureSubstring()
{
    var v = new List<string>();
    long ini = Environment.TickCount;
    foreach (string test in input)
        if (test.Length > 18)
        {
            v.Add(test.Substring(18));
        }
    //v.Count().Dump("entries with substring");
    //v.Take(5).Dump("entries with Sub");
    return Environment.TickCount - ini;
}
// Measure take time
private static long MeasureTake()
{
    var v = new List<string>();
    long ini = Environment.TickCount;
    foreach (string test in input)
        if (test.Length > 18) v.Add(new string(test.Take(18).ToArray()));
    //v.Count().Dump("entries with Take");
    //v.Take(5).Dump("entries with Take");
    return Environment.TickCount - ini;
}
// Create a list with random strings with random lengths
private static long BuildInput()
{
    long ini = Environment.TickCount;
    Random r = new Random();
    input = new List<string>();
    for (int i = 0; i < RETRIES; i++)
        input.Add(Guid.NewGuid().ToString().Substring(1,r.Next(0,36)));
    return Environment.TickCount - ini;
}

当使用Take方法时,在速度或内存使用方面是否有任何缺点

是的。Take()包括首先创建IEnumerator<char>,然后对于每个字符,经过MoveNext()yield return;等的循环。还要注意ToArray和字符串构造函数。

对于少量字符串来说不是问题,但是在大循环中,专用字符串函数要好得多。

Take扩展方法不创建子字符串,它返回一个可用于创建Char[] (ToArray)或List<Char> (ToList)的查询。但你需要的是那个子字符串

那么你还需要其他方法:

string  data = new string(temp.Take(20).ToArray());

这隐式地使用foreach来枚举字符,创建一个新的char[](由于加倍算法可能会分配太多的大小)。最后,从char[]创建一个新的字符串。

另一方面,Substring使用优化的方法。

所以你在内存方面付出了一点方便,这可能是可以忽略不计的,但并不总是。