1. 程式人生 > >聊聊字串拼接的哪一些事兒

聊聊字串拼接的哪一些事兒

​        字串對我程式設計人員來說是字串時每天見面的常客,你不認識不熟悉他都不得行,字串的拼接更是家常便飯,那麼在實際開發過程中實現字串的拼接有哪一些方式呢?咱們一起來聊聊,來交流溝通,學習一波。也許你會說,那也太簡單了嘛,誰不會啊,哈哈,使用起來確實簡單,但是不一定我們都使用的方式還有優秀的方式嗎?

    在文章前,我們先簡單聊聊關於string的資料型別儲存必須瞭解概念:

    string是一個引用型別,是一個sealed類,儲存在堆記憶體上,每一次修改都會從新建立一個新的string來儲存,原始的會自動被回收。這個是不感覺是廢話,人人都知道嘛,哈哈哈。

    下面以c#為開發語言來說明:實現字串的拼接常用的方式有如下四種

 

其一、直接通過+拼接

 

    直接通過+拼接是我們在程式碼中最常見的一種方式,下面以一個簡單的程式碼段來分析分析

    string str="1";

    str=str+"2";

    第一段程式碼,首先分配了一個記憶體空間來儲存str變數,其值為“1”

    第二段程式碼,重新分配了一個新的記憶體空間來儲存“12”,並將str指向新地址

    通過分析,其實我們不難發現,兩端就簡單的程式碼,就會有兩次記憶體地址操作,隨著拼接字串的個數地址,分配記憶體地址的次數也遞增,當幾個簡單的字串通過該方式拼接時,其實我們還是感覺不到效能的影響,但是當字串數量大時,你都會有感覺了,那樣不僅僅造成記憶體的浪費,還直接影響效能。

所以在實際開發工程中,通過+拼接字串比較常見,但是如果只是見到這種方式也就不那麼友好了,既然不友好,那麼顯然就會有比較友好的方式啦,下面我們就分析分析通過StringBuilder來實現字串的拼接。

 

其二、通過StringBuilder拼接字串

 

    StringBuilder其實內部相當於是維護的一個字元陣列,是一個可以動態增加自身資料長度,其預設長度為16,當儲存的字串超出其長度是,會自動擴容2倍長度。

    哈哈,說到這兒,估計你看出了問題,那就是超出長度自動擴容,自動擴容是不是也需要犧牲效能,當然在幾次擴容你還感覺不到效能的影響,但是如果詞數多了,你就會感覺很明顯,這也是對StringBuilder的一些使用技巧。

我們去看不同小夥伴的程式碼,你就會發現,技術老鳥,在初始化StringBuilder的時候會根據預估將要儲存的字串大小,給StringBuilder初始化一個長度,這也就是細節上的差距體現。

    說了半天的廢話,是不是要來的實際的程式碼來證明說的不是廢話呢?不急不急,在文章最後,我會專門寫測試程式碼對比分析的。

 

其三、string.Format不陌生吧

 

    對於一些格式的資料拼接填充,string.Format也是經常看見的,他的一個很大好處就是,看上去比較清晰

    其實我們看過string的底層實現我們會發現,其底層本質還是StringBuilder來實現的

    下面就是string.format的原始碼實現

public static String Format(IFormatProvider provider, String format, params Object[] args) <br>{

    if (format == null || args == null)

      throw new ArgumentNullException((format==null)?"format":"args");

    StringBuilder sb = new StringBuilder(format.Length + args.Length * 8);

    sb.AppendFormat(provider,format,args);

    return sb.ToString();

}
 

 

    其實string.Format使用起來很簡單,我就不在囉嗦介紹了,免得大家覺得煩,哈哈哈

    string result=string.Format("大家好,我叫{0},今年{1}","程式設計師修煉之旅",1);

 

其四、$方式拼接字串

 

    C#6.0出現了$方式拼接字串,其實簡單說就是string.Format簡化操作版,string.Format如果拼接的字串太多,估計自己都懵逼的分不清對應關係了,不知道你們遇到過沒有,反正我原來是遇到過的。$就很好的規避了該問題,那麼下    面來一個例子說明一切:

    

    string name = "程式設計師修煉之旅";    int age = 1;    string str = string.Format("my name is{0}, I'm {1} years old",name,age);    string str2 = $"my name is{name}, I'm {age} years old";    最終結果是:str=str1

 

其五,當然還有其他方式,不在此囉嗦了,後續在討論

 

測試分析

    說了半天,不拿點實際東西來測試,我知道你是不會信服的,下面就直接上測試程式碼:

using System;
using System.Diagnostics;
using System.Text;

namespace stringSplicingTest
{
    /// <summary>
    /// 字串拼接練習
    /// </summary>
    public class Program
    {
        /// <summary>
        /// 主函式入口
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // 測試分別通過+ 和 StringBuilder 來連線 0 之100的數字
            Console.WriteLine("測試分別通過+ 和 StringBuilder 來連線");
            Console.WriteLine("");
            Console.WriteLine("測試連線 0 - 100 的數字");
            Console.WriteLine("");
            PlusString(100);
            StringBuilderString2(100);
            Console.WriteLine("");

            Console.WriteLine("");
            Console.WriteLine("測試連線 0 - 10000 的數字");
            PlusString(10000);
            StringBuilderString2(10000);
            Console.WriteLine("");
            Console.WriteLine("");

            // 下面測試一下同樣是StringBuilder連線字串,一個是定義吃指定長度,一個是不指定長度對比
            Console.WriteLine(@"下面測試一下同樣是StringBuilder連線字串, 一個是定義並指定長度,一個是不指定長度對比");
            Console.WriteLine("");
            Console.WriteLine("測試連線 0 - 1000000 的數字");
            Console.WriteLine("不初始化長度");
            StringBuilderString(1000000);
            Console.WriteLine("初始化長度");
            StringBuilderString2(1000000);

            Console.WriteLine("");
            Console.WriteLine("");

            Console.WriteLine("測試連線 0 - 10000000 的數字");
            Console.WriteLine("不初始化長度");
            StringBuilderString(10000000);
            Console.WriteLine("初始化長度");
            StringBuilderString2(10000000);

            Console.ReadLine();
        }

        /// <summary>
        /// 通過+拼接字串
        /// </summary>
        /// <param name="totalNum"></param>
        private static void PlusString(int totalNum)
        {
            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//例項化類
            st.Start();//開始計時

            Console.WriteLine("開始執行,通過+連線字串:");
            string result = "";
            //// 定義一個數組
            for (int i = 0; i < totalNum; i++)
            {
                result = result + i.ToString();
            }

            //需要統計時間的程式碼段

            st.Stop();//終止計時
            Console.WriteLine(string.Format("執行完畢,通過+連線字串!總耗時{0}毫秒",
            st.ElapsedMilliseconds.ToString()));
        }

        /// <summary>
        /// 通過s拼接字串
        /// </summary>
        /// <param name="totalNum"></param>
        private static void StringBuilderString(int totalNum)
        {
            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//例項化類
            st.Start();//開始計時

            Console.WriteLine("開始執行,通過 StringBuilder 連線字串:");

            StringBuilder result = new StringBuilder();

            //// 定義一個數組
            for (int i = 0; i < totalNum; i++)
            {
                result.Append(i.ToString());
            }

            string result2 = result.ToString();
            //需要統計時間的程式碼段

            st.Stop();//終止計時
            Console.WriteLine(string.Format("執行完畢,通過 StringBuilder 連線字串!總耗時{0}毫秒",
            st.ElapsedMilliseconds.ToString()));
        }


        /// <summary>
        /// 通過StringBuilder拼接字串,初始化時指定一個長度
        /// </summary>
        /// <param name="totalNum"></param>
        private static void StringBuilderString2(int totalNum)
        {
            //// 定義一個秒錶,執行獲取執行時間
            Stopwatch st = new Stopwatch();//例項化類
            st.Start();//開始計時

            Console.WriteLine("開始執行,通過 StringBuilder 連線字串:");

            StringBuilder result = new StringBuilder(totalNum * 6);

            //// 定義一個數組
            for (int i = 0; i < totalNum; i++)
            {
                result.Append(i.ToString());
            }

            string result2 = result.ToString();
            //需要統計時間的程式碼段

            st.Stop();//終止計時
            Console.WriteLine(string.Format("執行完畢,通過 StringBuilder 連線字串!總耗時{0}毫秒",
            st.ElapsedMilliseconds.ToString()));
        }
    }
}

 


 

結果分析總結:

測試分兩個點:

其一測試的是:通過+和StringBuilder拼接字串的效能比較哦

其二測試的是:StringBuilder初始化長度和不初始化長度的效能比較

大概得出以下幾點結論

1、在待拼接的字串少的時,+和StringBuilder沒有明顯的效能差距

2、當拼接的字串多時,StringBuilder的優勢越來越明顯

3、同樣是StringBuilder拼接字串,預估初始化長度的效率比不初始化指定長度的效率高

說到此,我相信大家都知道該怎麼使用了。好了,時間不早了,趕緊洗洗睡了,明天還得上班呢?

 END

 

  歡迎各位小夥伴關注我的公眾號(程式設計師修煉之旅),裡面會分享一些技術類乾貨,同時也是一個技術溝通交流平臺,謝謝大家的支援。

&n