.NET面試題解析(03)-string與字符串操做

字符串能夠說是C#開發中最經常使用的類型了,也是對系統性能影響很關鍵的類型,熟練掌握字符串的操做很是重要。html

  常見面試題目:

1.字符串是引用類型類型仍是值類型?面試

2.在字符串鏈接處理中,最好採用什麼方式,理由是什麼?算法

3.使用 StringBuilder時,須要注意些什麼問題?編程

4.如下代碼執行後內存中會存在多少個字符串?分別是什麼?輸出結果是什麼?爲何呢?數組

string st1 = "123" + "abc";
string st2 = "123abc";
Console.WriteLine(st1 == st2);
Console.WriteLine(System.Object.ReferenceEquals(st1, st2));

5.如下代碼執行後內存中會存在多少個字符串?分別是什麼?輸出結果是什麼?爲何呢?函數

string s1 = "123";
string s2 = s1 + "abc";
string s3 = "123abc";
Console.WriteLine(s2 == s3);
Console.WriteLine(System.Object.ReferenceEquals(s2, s3));

6.使用C#實現字符串反轉算法,例如:輸入"12345", 輸出"54321"。性能

7.下面的代碼輸出結果?爲何?學習

object a = "123";
object b = "123";
Console.WriteLine(System.Object.Equals(a,b));
Console.WriteLine(System.Object.ReferenceEquals(a,b));
string sa = "123";
Console.WriteLine(System.Object.Equals(a, sa));
Console.WriteLine(System.Object.ReferenceEquals(a, sa));

  深刻淺出字符串操做

string是一個特殊的引用類型,使用上有點像值類型。之因此特殊,也主要是由於string太經常使用了,爲了提升性能及開發方便,對string作了特殊處理,給予了一些專用特性。爲了彌補string在字符串鏈接操做上的一些性能不足,便有了StringBuilder。優化

大笑 認識string

首先須要明確的,string是一個引用類型,其對象值存儲在託管堆中。string的內部是一個char集合,他的長度Length就是字符char數組的字符個數。string不容許使用new string()的方式建立實例,而是另外一種更簡單的語法,直接賦值(string aa= 「000」這一點也相似值類型)。ui

認識string,先從一個簡單的示例代碼入手:

public void DoStringTest()
{
    var aa = "000";
    SetStringValue(aa);
    Console.WriteLine(aa);
}

private void SetStringValue(string aa)
{
    aa += "111";
}

上面的輸出結果爲「000」。

經過前面的值類型與引用類型的文章,咱們知道string是一個引用類型,既然是一個引用類型,參數傳遞的是引用地址,那爲何不是輸出「000111」呢?是否是頗有值類型的特色呢!這一切的緣由源於string類型的兩個重要的特性:恆定性駐留性

書呆子 String的恆定性(不變性)

字符串是不可變的,字符串一經建立,就不會改變,任何改變都會產生新的字符串。好比下面的代碼,堆上先建立了字符串s1=」a」,加上一個字符串「b」後,堆上會存在三個個字符串實例,以下圖所示。

string s1 = "a";
string s2 = s1 + "b";

image

上文中的」任何改變都會產生新的字符串「,包括字符串的一些操做函數,如str1.ToLower,Trim(),Remove(int startIndex, int count),ToUpper()等,都會產生新的字符串,所以在不少編程實踐中,對於字符串忽略大小的比較:

if(str1.ToLower()==str2.ToLower()) //這種方式會產生新的字符串,不推薦
ifstring. Compare(str1,str2,true)) //這種方式性能更好

惱怒 String的駐留性

因爲字符串的不變性,在大量使用字符串操做時,會致使建立大量的字符串對象,帶來極大的性能損失。所以CLR又給string提供另一個法寶,就是字符串駐留,先看看下面的代碼,字符串s一、s2居然是同一個對象!

var s1 = "123";
var s2 = "123";
Console.WriteLine(System.Object.Equals(s1, s2));  //輸出 True
Console.WriteLine(System.Object.ReferenceEquals(s1, s2));  //輸出 True

相同的字符串在內存(堆)中只分配一次,第二次申請字符串時,發現已經有該字符串是,直接返回已有字符串的地址,這就是駐留的基本過程。

字符串駐留的基本原理:

  • CLR初始化時會在內存中建立一個駐留池,內部實際上是一個哈希表,存儲被駐留的字符串和其內存地址。
  • 駐留池是進程級別的,多個AppDomain共享。同時她不受GC控制,生命週期隨進程,意思就是不會被GC回收(不回收!難道不會形成內存爆炸嗎?不要急,且看下文)
  • 當分配字符串時,首先會到駐留池中查找,如找到,則返回已有相同字符串的地址,不會建立新字符串對象。若是沒有找到,則建立新的字符串,並把字符串添加到駐留池中。

若是大量的字符串都駐留到內存裏,而得不到釋放,不是很容易形成內存爆炸嗎,固然不會了?由於不是任何字符串都會駐留,只有經過IL指令ldstr建立的字符串纔會留用

字符串建立的有多種方式,以下面的代碼:

var s1 = "123";
var s2 = s1 + "abc";
var s3 = string.Concat(s1, s2);
var s4 = 123.ToString();
var s5 = s2.ToUpper();

其IL代碼以下

image

在上面的代碼中,出現兩個字符串常量,「123」和「abc」,這個兩個常量字符串在IL代碼中都是經過IL指令ldstr建立的,只有該指令建立的字符串纔會被駐留,其餘方式產生新的字符串都不會被駐留,也就不會共享字符串了,會被GC正常回收

那該如何來驗證字符串是否駐留呢,string類提供兩個靜態方法:

  • String.Intern(string str) 能夠主動駐留一個字符串;
  • String.IsInterned(string str);檢測指定字符串是否駐留,若是駐留則返回字符串,不然返回NULL

image

請看下面的示例代碼

var s1 = "123";
var s2 = s1 + "abc";
Console.WriteLine(s2);   //輸出:123abc
Console.WriteLine(string.IsInterned(s2) ?? "NULL");   //輸出:NULL。由於「123abc」沒有駐留

string.Intern(s2);   //主動駐留字符串
Console.WriteLine(string.IsInterned(s2) ?? "NULL");   //輸出:123abc

 

眨眼 認識StringBuilder

大量的編程實踐和意見中,都說大量字符串鏈接操做,應該使用StringBuilder。相對於string的不可變,StringBuilder表明可變字符串,不會像字符串,在託管堆上頻繁分配新對象,StringBuilder是個好同志。

首先StringBuilder內部同string同樣,有一個char[]字符數組,負責維護字符串內容。所以,與char數組相關,就有兩個很重要的屬性:

  • public int Capacity:StringBuilder的容量,其實就是字符數組的長度。
  • public int Length:StringBuilder中實際字符的長度,>=0,<=容量Capacity。

StringBuilder之因此比string效率高,主要緣由就是不會建立大量的新對象,StringBuilder在如下兩種狀況下會分配新對象:

  • 追加字符串時,當字符總長度超過了當前設置的容量Capacity,這個時候,會從新建立一個更大的字符數組,此時會涉及到分配新對象。
  • 調用StringBuilder.ToString(),建立新的字符串。

追加字符串的過程:

  • StringBuilder的默認初始容量爲16;
  • 使用stringBuilder.Append()追加一個字符串時,當字符數大於16,StringBuilder會自動申請一個更大的字符數組,通常是倍增;
  • 在新的字符數組分配完成後,將原字符數組中的字符複製到新字符數組中,原字符數組就被無情的拋棄了(會被GC回收);
  • 最後把須要追加的字符串追加到新字符數組中;

簡單來講,當StringBuilder的容量Capacity發生變化時,就會引發託管對象申請、內存複製等操做,帶來很差的性能影響,所以設置合適的初始容量是很是必要的,儘可能減小內存申請和對象建立。代碼簡單來驗證一下:

StringBuilder sb1 = new StringBuilder();
Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //輸出:Capacity=16; Length=0;   //初始容量爲16 
sb1.Append('a', 12);    //追加12個字符
Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //輸出:Capacity=16; Length=12;  
sb1.Append('a', 20);    //繼續追加20個字符,容量倍增了
Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //輸出:Capacity=32; Length=32;  
sb1.Append('a', 41);    //追加41個字符,新容量=32+41=73
Console.WriteLine("Capacity={0}; Length={1};", sb1.Capacity, sb1.Length); //輸出:Capacity=73; Length=73;  

StringBuilder sb2 = new StringBuilder(80); //設置一個合適的初始容量
Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //輸出:Capacity=80; Length=0;
sb2.Append('a', 12);
Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //輸出:Capacity=80; Length=12;
sb2.Append('a', 20);
Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //輸出:Capacity=80; Length=32;
sb2.Append('a', 41);
Console.WriteLine("Capacity={0}; Length={1};", sb2.Capacity, sb2.Length); //輸出:Capacity=80; Length=73;

爲何少許字符串不推薦使用StringBuilder呢?由於StringBuilder自己是有必定的開銷的,少許字符串就不推薦使用了,使用String.Concat和String.Join更合適。

 

吐舌笑臉 高效的使用字符串

  • 在使用線程鎖的時候,不要鎖定一個字符串對象,由於字符串的駐留性,可能會引起不能夠預料的問題;
  • 理解字符串的不變性,儘可能避免產生額外字符串,如:
if(str1.ToLower()==str2.ToLower()) //這種方式會產生新的字符串,不推薦
ifstring. Compare(str1,str2,true)) //這種方式性能更好
  • 在處理大量字符串鏈接的時候,儘可能使用StringBuilder,在使用StringBuilder時,儘可能設置一個合適的長度初始值;
  • 少許字符串鏈接建議使用String.Concat和String.Join代替。

  題目答案解析:

1.字符串是引用類型類型仍是值類型?

引用類型。

2.在字符串連加處理中,最好採用什麼方式,理由是什麼?

少許字符串鏈接,使用String.Concat,大量字符串使用StringBuilder,由於StringBuilder的性能更好,若是string的話會建立大量字符串對象。

3.使用 StringBuilder時,須要注意些什麼問題?

  • 少許字符串時,儘可能不要用,StringBuilder自己是有必定性能開銷的;
  • 大量字符串鏈接使用StringBuilder時,應該設置一個合適的容量;

4.如下代碼執行後內存中會存在多少個字符串?分別是什麼?輸出結果是什麼?爲何呢?

string st1 = "123" + "abc";
string st2 = "123abc";
Console.WriteLine(st1 == st2);
Console.WriteLine(System.Object.ReferenceEquals(st1, st2));

輸出結果:

True
True

內存中的字符串只有一個「123abc」,第一行代碼(string st1 = "123" + "abc"; )常量字符串相加會被編譯器優化。因爲字符串駐留機制,兩個變量st一、st2都指向同一個對象。IL代碼以下:

image

5.如下代碼執行後內存中會存在多少個字符串?分別是什麼?輸出結果是什麼?爲何呢?

string s1 = "123";
string s2 = s1 + "abc";
string s3 = "123abc";
Console.WriteLine(s2 == s3);
Console.WriteLine(System.Object.ReferenceEquals(s2, s3));

和第5題的結果確定是不同的,答案留給讀者吧,文章太長了,寫的好累!

6.使用C#實現字符串反轉算法,例如:輸入"12345", 輸出"54321"

這是一道比較綜合的考察字符串操做的題目,答案能夠有不少種。經過不一樣的答題能夠看出程序猿的基礎水平。下面是網上比較承認的兩種答案,效率上都是比較不錯的。

public static string Reverse(string str)
{
    if (string.IsNullOrEmpty(str))
    {
        throw new ArgumentException("參數不合法");
    }

    StringBuilder sb = new StringBuilder(str.Length);  //注意:設置合適的初始長度,能夠顯著提升效率(避免了屢次內存申請)
    for (int index = str.Length - 1; index >= 0; index--)
    {
        sb.Append(str[index]);
    }
    return sb.ToString();
}
public static string Reverse(string str)
{
    if (string.IsNullOrEmpty(str))
    {
        throw new ArgumentException("參數不合法");
    }
    char[] chars = str.ToCharArray();
    int begin = 0;
    int end = chars.Length - 1;
    char tempChar;
    while (begin < end)
    {
        tempChar = chars[begin];
        chars[begin] = chars[end];
        chars[end] = tempChar;
        begin++;
        end--;
    }
    string strResult = new string(chars);
    return strResult;
}

還有一個比較簡單也挺有效的方法:

public static string Reverse(string str)
{
    char[] arr = str.ToCharArray();
    Array.Reverse(arr);
    return new string(arr);
}

7.下面的代碼輸出結果?爲何?

object a = "123";
object b = "123";
Console.WriteLine(System.Object.Equals(a,b));
Console.WriteLine(System.Object.ReferenceEquals(a,b));
string sa = "123";
Console.WriteLine(System.Object.Equals(a, sa));
Console.WriteLine(System.Object.ReferenceEquals(a, sa));

輸出結果全是True,由於他們都指向同一個字符串實例,使用object聲明和string聲明在這裏並無區別(string是引用類型)。

使用object聲明和string聲明到底有沒有區別呢?,有點疑惑,一個朋友在面試時面試官有問過這個問題,那個面試官說sa、a是有區別的,且不相等。對於此疑問,歡迎交流。

版權全部,文章來源:http://www.cnblogs.com/anding

我的能力有限,本文內容僅供學習、探討,歡迎指正、交流。

.NET面試題解析(00)-開篇來談談面試 & 系列文章索引

  參考資料:

書籍:CLR via C#

書籍:你必須知道的.NET

深刻理解string和如何高效地使用string:  http://www.cnblogs.com/artech/archive/2007/05/06/737130.html

C#基礎知識梳理系列九:StringBuilder:http://www.cnblogs.com/solan/archive/2012/08/06/CSharp09.html

相關文章
相關標籤/搜索