zoukankan      html  css  js  c++  java
  • String.Join的实现

    String.Join的实现

    在开发中,有时候会遇到需要把一个List对象中的某个字段用一个分隔符拼成一个字符串的情况。比如在SQL语句的in条件中,我们通常需要把List<int>这样的对象转换为“1,2,3”这样的字符串,然后作为in的语句传进去。所以自然而然,可以通过循环的方式来拼着个字符串,于是可以写一个下面这样的通用方法:

    private static string GetStringFromList<T>(char seperator, IEnumerable<T> values)
    {
        if (seperator == null)
            return string.Empty;
    
        if (values == null && values.Count() == 0)
            throw new ArgumentNullException("values"); 
        String result;
        StringBuilder strBuilder;
    
        strBuilder = new StringBuilder();
        foreach (T str in values)
        {
            strBuilder.Append(str.ToString());
            strBuilder.Append(seperator);
        }
    
        result = strBuilder.ToString().TrimEnd(seperator);
    
        return result;
    }

    方法其实很简单,首先创建一个StringBuilder,然后再往里面Append数据,最后把最后多余的最后一个分隔符去除。

    后来发现BCL中string类型提供了现成的string.Join方法,该方法的功能和上面的方法相同。于是很好奇,想看看BCL中是如何实现这么一个简单的功能的,由于BCL的大部分代码已经开源,您可以使用Reflector这个工具查看,我之前就是使用这个工具,但是最近看到了微软的Reference Source 这个网站,可以在线查看源代码,比如string类的实现如下,您可以看到诸如string的GetHashCode是如何实现的等等, 这里我们回到我们想要查看的Join方法上来,其实现如下:

    [ComVisible(false)]
    public static String Join<T>(String separator, IEnumerable<T> values)
    {
        if (values == null)
            throw new ArgumentNullException("values");
        Contract.Ensures(Contract.Result<String>() != null);
        Contract.EndContractBlock();
    
        if (separator == null)
            separator = String.Empty;
    
        using (IEnumerator<T> en = values.GetEnumerator())
        {
            if (!en.MoveNext())
                return String.Empty;
    
            StringBuilder result = StringBuilderCache.Acquire();
            if (en.Current != null)
            {
                // handle the case that the enumeration has null entries
                // and the case where their ToString() override is broken
                string value = en.Current.ToString();
                if (value != null)
                    result.Append(value);
            }
    
            while (en.MoveNext())
            {
                result.Append(separator);
                if (en.Current != null)
                {
                    // handle the case that the enumeration has null entries
                    // and the case where their ToString() override is broken
                    string value = en.Current.ToString();
                    if (value != null)
                        result.Append(value);
                }
            }
            return StringBuilderCache.GetStringAndRelease(result);
        }
    }

    代码是不是很简单。对比之前手动实现的方法,发现自己写的代码看起来很挫,这个就是差距,String的Join方法中我们可以看到一下几个地方值得注意:

    1. 在方法的开始处,使用了Contract 这个类来进行验证协助代码的编写,这个在之前的文章中有所介绍,这里使用了后置条件判断,表示方法的返回值需要是string类型,并且不为空;还有就是在方法开始处做必要的参数合法性验证;在方法中及时判断,及时返回。
    2. 在实现中,使用了枚举器,C#中的foreach语句其实就是这种枚举器的语法糖,所以这里没有什么好说的,值得一提的是在while循环中的判断语句while(en.MoveNext) 很好的避免了我们方法中在字符串末尾添加多余的字符串,最后还要调用TrimEnd的这种无谓的内存开销。这其实也是do{…}while(..),和while(…){…}这两种循环体的差异体现。
    3. 实现中,没有直接new直接分配StringBuilder,在返回字符串时也没有直接使用ToString方法,而是使用了StringBuilderCache这个类,这个在之前翻译的.NET程序的性能要领和优化建议 这篇文章中有所介绍。

    这个类一看就是对StringBuilder的缓存,因为对于一些小的字符串,创建StringBuilder也是一笔开销。StringBuilder的实现如下:

    // ==++==
    // 
    //   Copyright (c) Microsoft Corporation.  All rights reserved.
    // 
    // ==--==
    /*============================================================
    **
    ** Class:  StringBuilderCache
    **
    ** Purpose: provide a cached reusable instance of stringbuilder
    **          per thread  it's an optimisation that reduces the 
    **          number of instances constructed and collected.
    **
    **  Acquire - is used to get a string builder to use of a 
    **            particular size.  It can be called any number of 
    **            times, if a stringbuilder is in the cache then
    **            it will be returned and the cache emptied.
    **            subsequent calls will return a new stringbuilder.
    **
    **            A StringBuilder instance is cached in 
    **            Thread Local Storage and so there is one per thread
    **
    **  Release - Place the specified builder in the cache if it is 
    **            not too big.
    **            The stringbuilder should not be used after it has 
    **            been released.
    **            Unbalanced Releases are perfectly acceptable.  It
    **            will merely cause the runtime to create a new 
    **            stringbuilder next time Acquire is called.
    **
    **  GetStringAndRelease
    **          - ToString() the stringbuilder, Release it to the 
    **            cache and return the resulting string
    **
    ===========================================================*/
    using System.Threading;
     
    namespace System.Text
    {
        internal static class StringBuilderCache
        {
            // The value 360 was chosen in discussion with performance experts as a compromise between using
            // as litle memory (per thread) as possible and still covering a large part of short-lived
            // StringBuilder creations on the startup path of VS designers.
            private const int MAX_BUILDER_SIZE = 360;
     
            [ThreadStatic]
            private static StringBuilder CachedInstance;
     
            public static StringBuilder Acquire(int capacity = StringBuilder.DefaultCapacity)
            {
                if(capacity <= MAX_BUILDER_SIZE)
                {
                    StringBuilder sb = StringBuilderCache.CachedInstance;
                    if (sb != null)
                    {
                        // Avoid stringbuilder block fragmentation by getting a new StringBuilder
                        // when the requested size is larger than the current capacity
                        if(capacity <= sb.Capacity)
                        {
                            StringBuilderCache.CachedInstance = null;
                            sb.Clear();
                            return sb;
                        }
                    }
                }
                return new StringBuilder(capacity);
            }
     
            public static void Release(StringBuilder sb)
            {
                if (sb.Capacity <= MAX_BUILDER_SIZE)
                {
                    StringBuilderCache.CachedInstance = sb;
                }
            }
     
            public static string GetStringAndRelease(StringBuilder sb)
            {
                string result = sb.ToString();
                Release(sb);
                return result;
            }
        }
    }

    这里面对StringBuilder的创建和字符串获取进行了缓存。 代码的注释很清楚,这里就不多讲了。

    .NET的源代码大部分都可以直接看了,以前可以使用Reflector进行查看,现在Reference Source 这个网站可以在线查看源代码以及详细的注释信息,看看代码对自己的提高还是挺有帮助的。

  • 相关阅读:
    Selenium2+python自动化17-JS处理滚动条
    图论一
    HDU1106
    银行家算法学习笔记
    NYOJ 540
    我在博客园 2013-08-02 22:04 200人阅读 评论(0) 收藏
    编程之美:平面最近点对
    RIA算法解决最小覆盖圆问题
    求两直线交点和三角形内外心
    求圆心
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3860685.html
Copyright © 2011-2022 走看看