23

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

如果您执行 temp.substring(0,20) 但 temp 仅包含 10 个字符,则会引发异常。

我看到了 2 个解决方案:

  1. 测试长度,并在需要时执行子字符串
  2. 使用扩展方法Take

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

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

4

6 回答 6

24

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

例如:

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);
            }
        }
    }
}
于 2013-03-14T10:05:57.203 回答
14

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

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

我编写了一个示例程序来准确地测试该方法的速度有多慢Take(),结果如下:

一千万次测试:

  • 执行子串的时间:266 ms
  • 执行 take 操作的时间: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;
    }
于 2013-03-14T10:08:02.173 回答
6

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

你在谈论性能/内存问题。对。正如其他人所说,string.SubString效率更高,因为它是如何在内部优化的,以及 LINQ 的工作方式string.Take()(字符枚举等)。

没有人说的是,Take()在您的情况下的主要缺点是它完全破坏了 substring 的简单性。正如蒂姆所说,要获得您想要的实际字符串,您必须编写:

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

该死的......这比(参见马修的扩展方法)更难理解:

string myString = temp.Left(20);

LINQ 非常适合许多用例,但如果没有必要,则不应使用。即使是一个简单的循环有时也比 LINQ 更好(即更快、更易读/易理解),所以想象一个简单的子字符串......

总结一下您的情况下的LINQ:

  • 表现更差
  • 可读性较差
  • 不太容易理解
  • 需要 LINQ(例如,不适用于 .Net 2.0)
于 2013-03-14T10:44:49.767 回答
3

@Daniel 答案的变体对我来说似乎更准确。
Guid 的长度是 36。我们正在创建一个列表,其中包含从 1 到 36 的可变长度字符串,我们的目标是使用substring/take方法获取 18,所以大约一半会通过。

我得到的结果表明Take将比. Substring

结果示例:

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 微秒。如果你觉得你需要将它削减 5 以获取子字符串,那就去吧,但我怀疑在现实生活中,除了紧身衣循环之外,你会感觉到不同。

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;
}
于 2017-07-05T03:26:28.837 回答
2

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

是的。Take()涉及创建IEnumerator<char>第一个,并且对于每个字符,通过等的箍MoveNext()yield return;还要注意 ToArray 和字符串构造函数。

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

于 2013-03-14T10:03:03.307 回答
1

Take扩展方法不创建子字符串,它返回可用于创建Char[](ToArray) 或List<Char>(ToList)的查询。但是您实际上想要拥有该子字符串。

然后你还需要其他方法:

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

这隐含地使用 aforeach来枚举字符,创建一个新的 char[] (由于加倍算法,它可能分配过多的大小)。最后从char[].

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

因此,您可以使用可能微不足道但并非总是如此的内存来支付这个小小的便利。

于 2013-03-14T10:05:21.523 回答