string.substring 与 string.take

Posted

技术标签:

【中文标题】string.substring 与 string.take【英文标题】:string.substring vs string.take 【发布时间】:2013-03-02 14:42:55 【问题描述】:

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

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

我看到了 2 个解决方案:

    测试长度,如果需要,做子串

    使用扩展方法采取

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

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

【问题讨论】:

使用秒表进行计时。此外,您可以为执行检查的字符串编写自己的扩展方法。 我会说 Take 会枚举你的字符串。这可能会对长字符串产生巨大的影响。 性能非常符合上下文。 temp.SubString(0, Math.Min(20, temp.Length)) 【参考方案1】:

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

我得到的结果表明Take 将比Substring6-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 微秒。如果你觉得你需要为子字符串减少 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;

【讨论】:

【参考方案2】:

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

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

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

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

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

string myString = temp.Left(20);

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

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

表现更差 可读性较差 难以理解 需要 LINQ(例如,不适用于 .Net 2.0)

【讨论】:

可以使用一个封装了字符串构造函数的扩展方法:public static string StringJoin(this IEnumerable&lt;char&gt; chars) return new string(chars.ToArray()); ,然后使用如下:string myString = temp.Take(20).StringJoin();如果是可读性,那我觉得这个方案比较优雅,否则LINQ与Substring相比,速度太慢了【参考方案3】:

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

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

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

一千万次测试:

执行子字符串的时间:266 毫秒 执行拍摄操作的时间:1437 毫秒

这里是代码:

    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;
    

【讨论】:

您的代码不会执行 SubString 调用,因为 GUID 总是超过 4 个字符。这会使您的测量无效;) 哇 .. 4 年后,但是嘿... 为什么不... 你一遍又一遍地测试相同的字符串,得到相同的结果...我已经添加了一个answer,它将创建一个不同长度的输入字符串列表,然后使用更多的熵执行substring/take。结果表明 Take 慢了 6-10 倍,但仍然非常快(每个 take 不到 0.0008 毫秒)。【参考方案4】:

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

例如:

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);
            
        
    

【讨论】:

这确实是我的首选解决方案,它比 Take 更具可读性,并且使用了 substring 方法的强大功能。谢谢大家的信息【参考方案5】:

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

是的。 Take() 涉及首先创建一个IEnumerator&lt;char&gt;,并且对于每个字符,都要经过MoveNext()yield return; 等的循环。还要注意ToArray 和字符串构造函数。

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

【讨论】:

是的。收益回报。 linq 的可爱 => 收益回报【参考方案6】:

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

那么你还需要其他方法:

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

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

另一方面,Substring 使用 optimized methods。

因此,您为这一点点的便利支付了可能微不足道但并非总是如此的内存。

【讨论】:

以上是关于string.substring 与 string.take的主要内容,如果未能解决你的问题,请参考以下文章

截取字符串substring与substr之间的区别

String.subString() 和 String.subSequence() 有啥区别

java String.substring 乱码

Java:具有长类型参数的 String.substring()

为啥 string.Substring 不与源字符串共享内存?

陕西柴油机--机械ip--------》QQ请求汇创