[读书笔记]C#学习笔记八:StringBuilder与String详解及参数传递问题剖析

前言

上次在公司开会时有同事分享windebug的知识, 拿的是string字符串Concat拼接 然后用while(true){}死循环的Demo来讲解.
其中有提及string操作大量字符串效率低下的问题, 刚好自己之前也看过类似的问题, 于是便拿出来记录一下.
本文内容: 参数传递问题剖析, string与stringbuilder详解

1,参数传递问题剖析

对于C#中的参数传递,根据参数的类型可以分为四类:

  • 值类型参数的按值传递
  • 引用类型参数的按值传递
  • 值类型参数的按引用传递
  • 引用类型参数的按引用传递

1.1值类型参数的按值传递

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5
 6         int addNum = 1;
 7         // addNum 就是实参,
 8         Add(addNum);
 9      }
10
11     // addnum就是形参,也就是被调用方法中的参数
12     private static void Add(int addnum)
13     {
14         addnum = addnum + 1;
15         Console.WriteLine(addnum);
16     }
17 }

对于值类型的按值传递,传递的是该值类型实例的一个拷贝,也就是形参此时接受到的是实参的一个副本,被调用方法操作是实参的一个拷贝,所以此时并不影响原来调用方法中的参数值,为了证明这点,看看下面的代码和运行结果就明白了:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         // 1. 值类型按值传递情况
 6         Console.WriteLine("按值传递的情况");
 7         int addNum = 1;
 8         Add(addNum);
 9         Console.WriteLine(addNum);
10
11         Console.Read();
12     }
13
14     // 1. 值类型按值传递情况
15     private static void Add(int addnum)
16     {
17         addnum = addnum + 1;
18         Console.WriteLine(addnum);
19     }
20 }

运行结果是: 
按值传递的情况
2
1
从结果中可以看出addNum调用方法之后它的值并没有改变,Add 方法的调用只是改变了addNum的副本addnum的值,所以addnum的值修改为2了。具体的分析请看下面的图:

1.2引用类型参数的按值传递
当传递的参数是引用类型的时候,传递和操作的是指向对象的引用(看到这里,有些朋友会觉得此时不是传递引用吗?怎么还是按值传递了?对于这个疑惑,此时确实是按值传递,此时传递的对象的地址,传递地址本身也是传递这个地址的值,所以此时仍然是按值传递的),此时方法的操作就会改变原来的对象。代码如下:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         // 2. 引用类型按值传递情况
 6         RefClass refClass = new RefClass();
 7         AddRef(refClass);
 8         Console.WriteLine(refClass.addnum);
 9     }
10      // 2. 引用类型按值传递情况
11     private static void AddRef(RefClass addnumRef)
12     {
13         addnumRef.addnum += 1;
14         Console.WriteLine(addnumRef.addnum);
15     }
16 }
17 class RefClass
18 {
19     public int addnum=1;
20 }

运行结果为:
2
2
为什么此时传递引用就会修改原来实参中的值呢?对于这点我们还是参数在内存中分布图来解释下:

1.3string引用类型参数的按值传递的特殊情况
对于String类型同样是引用类型,然而对于string类型的按值传递时,此时引用类型的按值传递却不会修改实参的值,可能很多朋友对于这点很困惑,下面具体看看下面的代码:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5           // 3. String引用类型的按值传递的特殊情况
 6         string str = "old string";
 7         ChangeStr(str);
 8         Console.WriteLine(str);
 9
10     }
11
12      // 3. String引用类型的按值传递的特殊情况
13     private static void ChangeStr(string oldStr)
14     {
15         oldStr = "New string";
16         Console.WriteLine(oldStr);
17     }
18 }

运行结果为:

New string
old string
对于为什么原来的值没有被改变主要是因为string的“不变性”,所以在被调用方法中执行 oldStr="New string"代码时,此时并不会直接修改oldStr中的"old string"值为"New string",因为string类型是不变的,不可修改的,此时内存会重新分配一块内存,然后把这块内存中的值修改为 “New string”,然后把内存中地址赋值给oldStr变量,所以此时str仍然指向 "old string"字符,而oldStr却改变了指向,它最后指向了 "New string"字符串。所以运行结果才会像上面这样,下面内存分布图可以帮助你更形象地理解文字表述:

1.4按引用传递

不管是值类型还是引用类型,我们都可以使用ref 或out关键字来实现参数的按引用传递,然而按引用进行传递的时候,需要注意下面两点:

方法的定义和方法调用都必须同时显式使用ref或out,否则会出现编译错误

CLR允许通过out 或ref参数来实现方法重载。如:

 1 #region CLR 允许out或ref参数来实现方法重载
 2 private static void Add(string str)
 3 {
 4     Console.WriteLine(str);
 5 }
 6
 7 // 编译器会认为下面的方法是另一个方法,从而实现方法重载
 8 private static void Add(ref string str)
 9 {
10     Console.WriteLine(str);
11 }
12 #endregion

按引用传递可以解决由于值传递时改变引用副本而不影响引用本身的问题,此时传递的是引用的引用(也就是地址的地址),而不是引用的拷贝(副本)。下面就具体看看按引用传递的代码:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         #region 按引用传递
 6         Console.WriteLine("按引用传递的情况");
 7         int num = 1;
 8         string refStr = "Old string";
 9         ChangeByValue(ref num);
10         Console.WriteLine(num);
11         changeByRef(ref refStr);
12         Console.WriteLine(refStr);
13         #endregion
14
15         Console.Read();
16     }
17
18     #region 按引用传递
19     // 1. 值类型的按引用传递情况
20     private static void ChangeByValue(ref int numValue)
21     {
22         numValue = 10;
23         Console.WriteLine(numValue);
24     }
25
26     // 2. 引用类型的按引用传递情况
27     private static void changeByRef(ref string numRef)
28     {
29         numRef = "new string";
30         Console.WriteLine(numRef);
31     }
32
33     #endregion
34 }

运行结果为:
按引用传递的情况
10
10
new string
new string
从运行结果可以看出,此时引用本身的值也被改变了,通过下面一张图来帮忙大家理解下按引用传递的方式:

到这里参数的传递所有内容就介绍完了。总之,对于按值传递,不管是值类型还是引用类型的按值传递,都是传递实参的一个拷贝,只是值类型时,此时传递的是实参实例的一个拷贝(也就是值类型值的一个拷贝),而引用类型时,此时传递的实参引用的副本。对于按引用传递,传递的都是参数地址,也就是实例的指针。

2, string与stringBuilder的内部实现
大家应该知道如果做大量的字符串拼接的话, string的效率明显是低于stringBuilder的, 至于示例我这里就不在列出了,下面给出个链接可以查看下.
我这里只是从string和stringBuilder源码说起, 通过源代码的实现方式来说明stringBuilder为何比string效率高.

StringBuilder vs String+String(String concatenation):
通常情况下,4~8个字符串之间的连接,String+String的效率更高。
答案来自: http://stackoverflow.com/a/1612819
StringBuilder vs String.concat():
如果在编译期间不能确定要连接的字符串个数,用StringBuilder更合适。
答案来自: http://stackoverflow.com/a/4191142

下面先给出结论:

stringbuilder内部维护一个字符数组。下次追加的字符串,直接占用空余的位置。 
如果超出上限。数组增大为原来的两倍(两倍还不够就直接增大到足够宽度),然后覆盖原来的数组.

String是不可改变的。每次使用System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。
在需要对字符串执行重复修改的情况下,与创建新的String对象相关的系统开销可能会非常昂贵。

那么下面就看看string和stringBuilder源码有和区别吧, 我这里是使用的Reflector查看的:
(1)string

打开Reflector,找到string类

找到Concat方法, 我们这里以Concat为例:

下面我们在看下FillStringChecked(dest, 0, str0)的实现方式:

所以看到这里结论就出来了: 当我们队字符串进行大量操作的时候, 会产生很多的新的字符串, 这些字符串会大量零碎的占据着堆空间, 大多都是生存期较短的, 会对gc产生比较大的回收压力.

(2)stringBuilder

看这个类的话,还是看一下它的源代码,以Append吧,从下面这个截图中看出来几个有意思的地方。

<1> 原来StringBuilder里面维护的是一个m_ChunkChars的字符数组。

<2> 如果当前的字符串的length<2,会直接给chunkchars数组复制,length>2的时候看到的是刚才string类中经典的wstrcpy用法,而

这个时候ptr指向的是chunkChars[chunkLength]的首地址,而不像string中申请新的内存空间,所以从这里看,比string大大的节省

    了内存空间。

更多细节内容请看@老赵点滴 大神的博客内容吧:

重谈字符串连接性能上:http://blog.zhaojie.me/2009/11/string-concat-perf-1-benchmark.html
重谈字符串连接性能中:http://blog.zhaojie.me/2009/12/string-concat-perf-2-stringbuilder-implementations.html
重谈字符串连接性能下:http://blog.zhaojie.me/2009/12/string-concat-perf-3-profiling-analysis.html

PS:好了, 到了这里@Learning Hard的<<C#读书笔记>> 的读书笔记就分享完了. 后面开始自己学Asp.Net(以前学的是java, 接触最多的是jsp, 到了公司开始做.Net), 对于Asp.Net还不是太了解, 希望用一段时间可以掌握这个. 另外空闲时间还在读<<CLR via C#>>这本书, 很不错的一本书, 需要慢慢去消化.

口语小贴士:
Be my guest.
请便,别客气.
Boy will be boys.
本性难移!
Don‘t beat around the bush.
别拐弯抹角了.
Don‘t bury your head in the sand.
不要逃避现实了.
Don‘t get me wrong.
不要误会我
Don‘t get on my nerves!
不要搅的我心烦
Don‘t look wise.
别自作聪明.

时间: 2024-08-07 08:39:42

[读书笔记]C#学习笔记八:StringBuilder与String详解及参数传递问题剖析的相关文章

Linux 程序设计学习笔记----终端及串口编程基础之概念详解

转载请注明出处,谢谢! linux下的终端及串口的相关概念有: tty,控制台,虚拟终端,串口,console(控制台终端)详解 部分内容整理于网络. 终端/控制台 终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念. 1.终端 一台主机,连很多终端,终端为主机提供了人机接口,每个人都通过终端使用主机的资源. 终端有字符哑终端和图形终端两种. 控制台是另一种人机接口, 不通过终端与主机相连, 而是通过显示卡-显示器和键盘接口分别与主机相连, 这是人控制主机的第一人机接口.

contiki-main.c 中的process系列函数学习笔记 &lt;contiki学习笔记之六&gt;

说明:本文依然依赖于 contiki/platform/native/contiki-main.c 文件. ------------------------------------------------------------------------------------------------------------------------------------- 根据上一个笔记里面添加的printf()语句的打印信息提示,hello world 打印是在执行了 1 autostart_

qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

原博主博客地址:http://blog.csdn.net/qq21497936本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78516201 qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等) 本学章节笔记主要详解Item元素(上半场主要涉及anchors锚),因为所有可视化的界面元素都继承于Item,熟悉Item后,不同的继承子类,有其定制的属性(从几个到几十个不等). <Qt实用技巧:在Qt Gui程

【linux_笔记】Linux_文件查找(find)详解&&特殊权限

学习资源来自:www.magedu.com 学习记录过程中难免出现错误,如有发现,还望大神们指出. 示例操作部分有的与历史操作有关,如果先前的示例操作没有执行过的话,可能会有部分示例的操作无法执行.示例仅供参考(练习题在附录). 文件查找: locate(不常用):非实时,模糊匹配,根据全系统文件数据库进行查找,速度快:# updatedb, 手动生成文件数据库(非常耗时) find:实时,精确,支持众多查找标准,遍历指定目录中的所有文件完成查找,速度慢: 命令格式:find 查找路径 查找标准

笔记-【3】-event事件对象的详解!

event事件对象:是指当前对象发生的事件的一些详细的信息在event这个对象里. event对象从哪里来?从事件函数中传入 obj. //e就会当前的事件对象event } 对象就有属性和方法:那么event对象也有属性和方法 event的属性和方法: { 属性: button :  当前事件的方法中判断鼠标的按键位置 有三个值: 0 (左键) 1(滚轮) 2(右键) ctrlkey:  判断是否按下了ctrl键; altkey:  判断是否按下了alt键; shiftkey:  判断是否按下

iOS回顾笔记(04) -- UIScrollView的基本使用详解

html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption

JavaEE学习之Maven配置文件pom.xml详解(转)

一.引言 (本文转载自:http://blog.csdn.net/longeremmy/article/details/9670619) 使用maven有一些时间了,一直没有好好将pom配置文件每个节点的意义好好了解一番.今天突然想来了解下:pom- project object model 项目对象模型.顾名思义,他是用来描述项目信息的,以及构建方式,依赖等.网上有一篇文章写的很详细,这里就借用一下,以备日后使用. 二.详解 1 <project xmlns="http://maven.

Spring4.0MVC学习资料,Controller中的方法详解和使用(四)

在以前,mvc的框架,基本上就是struts框架了.但是现在不一样了.springmvc出来了.spring的mvc框架不亚于struts了,springmvc出来了,我们有了更多的选择. Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面.Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块.使用 Spring 可插入的 MVC 架构,可以选择是使用内置的 Spring Web 框架还可以是 Struts 这样的 We

Spring4.0MVC学习资料,ApplicationContext中的方法详解(三)

做为java开源的一部分,spring框架一直排在老大的位置.Spring4.0 是 Spring 推出的一个重大版本升级,进一步加强了 Spring 作为 Java 领域第一开源平台的地位.Spring4.0 引入了众多 Java 开发者期盼的新特性,如泛型依赖注入.SpEL.校验及格式化框架.Rest风格的 WEB 编程模型等.这些新功能实用性强.易用性高,可大幅降低 JavaEE 开发的难度,同时有效提升应用开发的优雅性.为了方便开发,Spring的ApplicationContext类,