string是一种很特殊的数据类型,它既是基元类型又是引用类型,在编译以及运行时,.Net都对它做了一些优化工作,正式这些优化工作有时会迷惑编程人员,使string看起来难以琢磨,这篇文章分上下两章,共四节,来讲讲关于string的陌生一面。
  一.恒定的字符串
  要想比较全面的了解stirng类型,首先要清楚.Net中的值类型与引用类型。在C#中,以下数据类型为值类型:
    bool、byte、char、enum、sbyte以及数字类型(包括可空类型)
  以下数据类型为引用类型:
    class、interface、delegate、object、stirng
  看到了吗,我们要讨论的stirng赫然其中。被声明为string型变量存放于堆中,是一个彻头彻尾的引用类型。
  那么许多同学就会对如下代码产生有疑问了,难道string类型也会“牵一发而动全身”吗?让我们先来看看以下三行代码有何玄机:
    string a = "str_1";
    string b = a;
    a = "str_2";

  不要说无聊,这一点时必须讲清楚的!在以上代码中,第3行的“=”有一个隐藏的秘密:它的作用我们可以理解为新建,而不是对变量“a”的修改。以下是IL代码,可以说明这一点:
      .maxstack  1
      .locals init ([0] string a,
               [1] string b)
      IL_0000:  nop
      IL_0001:  ldstr      "str_1"
      IL_0006:  stloc.0
      IL_0007:  ldloc.0
      IL_0008:  stloc.1
      IL_0009:  ldstr      "str_2"
        IL_000e:  stloc.0  //以上2行对应 C#代码 a = "str_2";
      IL_0015:  ret
  可以看出IL代码的第1、6行,由ldstr指令创建字符串"str_1",并将其关联到了变量“a”中;7、8行直接将堆栈顶部的值弹出并关联到变量“b”中;9、10由ldstr创建字符串"str_2",关联在变量“a”中(并没有像我们想象的那样去修改变量a的旧值,而是产生了新的字符串); 
  在C#中,如果用new关键字实例化一个类,对应是由IL指令newobj来完成的;而创建一个字符串,则由ldstr指令完成,看到ldstr指令,我们即可认为,IL希望创建一个新的字符串 。(注意:是IL希望创建一个字符串,而最终是否创建,还要在运行时由字符串的驻留机制决定,这一点下面的章节会有介绍。)
所以,第三行C#代码(a = "str_2";)的样子看起来是在修改变量a的旧值"str_1",但实际上是创建了一个新的字符串"str_2",然后将变量a的指针指向了"str_2"的内存地址,而"str_1"依然在内存中没有受到任何影响,所以变量b的值没有任何改变---这就是string的恒定性,同学们,一定要牢记这一点,在.Net中,string类型的对象一旦创建即不可修改!包括ToUpper、SubString、Trim等操作都会在内存中产生新的字符串。
  本节重点回顾:由于stirng类型的恒定性,让同学友们经常误解,string虽属引用类型但经常表现出值的特性,这是由于不了解string的恒定性造成的,根本不是“值的特性”。例如:
    string a = "str_1";
    a = "str_2";
  这样会在内存中创建"str_1"和"str_2"两个字符串,但只有"str_2"在被使用,"str_1"不会被修改或消失,这样就浪费了内存资源,这也是为什么在做大量字符串操作时,推荐使用StringBuilder的原因。
  二..Net中字符串的驻留(重要)
  在第一节中,我们讲了字符串的恒定性,该特性又为我们引出了字符串的另一个重要特性:字符串驻留。
  从某些方面讲,正是字符串的恒定性,才造就了字符串的驻留机制,也为字符串的线程同步工作大开方便之门(同一个字符串对象可以在不同的应用程序域中被访问,所以驻留的字符串是进程级的,垃圾回收不能释放这些字符串对象,只有进程结束这些对象才被释放)。
  我们用以下2行代码来说明字符串的驻留现象:
    string a = "str_1";
    string b = "str_1";
  请各位同学友思考一下,这2行代码会在内存中产生了几个string对象?你可能会认为产生2个:由于声明了2个变量,程序第1行会在内存中产生"str_1"供变量a所引用;第2行会产生新的字符串"str_1"供变量b所引用,然而真的是这样吗?我们用ReferenceEquals这个方法来看一下变量a与b的内存引用地址:
    string a = "str_1";
    string b = "str_1";
    Response.Write(ReferenceEquals(a,b));   //比较a与b是否来自同一内存引用
    输出:True
  哈,各位同学看到了吗,我们用ReferenceEquals方法比较a与b,虽然我们声明了2个变量,但它们竟然来自同一内存地址!这说明string b = "str_1";根本没有在内存中产生新的字符串。
  这是因为,在.Net中处理字符串时,有一个很重要的机制,叫做字符串驻留机制。由于string是编程中用到的频率较高的一种类型,CLR对相同的字符串,只分配一次内存。CLR内部维护着一块特殊的数据结构,我们叫它字符串池,可以把它理解成是一个HashTable,这个HashTable维护着程序中用到的一部分字符串,HashTable的Key是字符串的值,而Value则是字符串的内存地址。一般情况下,程序中如果创建一个string类型的变量,CLR会首先在HashTable遍历具有相同Hash Code的字符串,如果找到,则直接把该字符串的地址返回给相应的变量,如果没有才会在内存中新建一个字符串对象。
  所以,这2行代码只在内存中产生了1个string对象,变量b与a共享了内存中的"str_1"。
  好了,结合第一节所讲到的字符串恒定性与第二节所讲到的驻留机制,来理解一下下面4行代码吧:
    string a = "str_1"; //声明变量a,将变量a的指针指向内存中新产生的"str_1"的地址
    a = "str_2";  //CLR先会在字符串池中遍历"str_2"是否已存在,如果没有,则新建"str_2",并修改变量a的指针,指向"str_2"内存地址,"str_1"保持不变。(字符串恒定)
    string c = "str_2"; //CLR先会在字符串池中遍历"str_2"是否已存在,如果存在,则直接将变量c的指针指向"str_2"的地址。(字符串驻留)

  那么如果是动态创建字符串呢?字符串还会不会有驻留现象呢?
  我们分3种情况讲解动态创建字符串时,驻留机制的表现:

  字符串常量连接
    string a = “str_1” + “str_2”;
    string b = “str_1str_2”;
    Response.Write(ReferenceEquals(a,b));   //比较a与b是否来自同一内存引用
    输出 :True
  IL代码说明问题:
    .maxstack  1
    .locals init ([0] string a,
               [1] string b)
    IL_0000:  nop
    IL_0001:  ldstr      “str_1str_2”
    IL_0006:  stloc.0
    IL_0007:  ldstr      “str_1str_2”
    IL_000c:  stloc.1
    IL_000d:  ret
  其中第1、6行对应c#代码string a = “str_1” + “str_2”;
  第7、8对应c# string b = “str_1str_2”;
  可以看出,字符串常量连接时,程序在被编译为IL代码前,编译器已经计算出了字符串常量连接的结果,ldstr指令直接处理编译器计算后的字符串值,所以这种情况字符串驻留机制有效!
  字符串变量连接
    string a = “str_1”;
    string b = a + “str_2”;
    string c = “str_1str_2”;
    Response.Write(ReferenceEquals(b,c));
    输出:False

  IL代码说明问题:
      .maxstack  2
      .locals init ([0] string a,
               [1] string b,
               [2] string c)
     IL_0000:  nop
      IL_0001:  ldstr      “str_1”
      IL_0006:  stloc.0
      IL_0007:  ldloc.0
      IL_0008:  ldstr      “str_2”
      IL_000d:  call       string [mscorlib]System.String::Concat(string,
                                                                  string)
      IL_0012:  stloc.1
      IL_0013:  ldstr      “str_1str_2”
      IL_0018:  stloc.2
      IL_0019:  ret

  其中第1、6行对应string a = “str_1”;
  第7、8、9行对应string b = a + “str_2”;,IL用的是Concat方法连接字符串
  第13、18行对应string c = “str_1str_2”;
  可以看出,字符串变量连接时,IL使用Concat方法,在运行时生成最终的连接结  果,所以这种情况字符串驻留机制无效!
  3.显式实例化

    string a = "a";
    string b = new string('a',1);
    Response.Write(ReferenceEquals(a, b));

    输出 False

  IL代码:
      .maxstack  3
      .locals init ([0] string a,
               [1] string b)
      IL_0000:  nop
      IL_0001:  ldstr      "a"
      IL_0006:  stloc.0
      IL_0007:  ldc.i4.s   97
      IL_0009:  ldc.i4.1
      IL_000a:  newobj     instance void [mscorlib]System.String::.ctor              (char,
                                                                    int32)
      IL_000f:  stloc.1
      IL_0010:  ret

  这种情况比较好理解,IL使用newobj来实例化一个字符串对象,驻留机制无效。从string b = new string('a',1);这行代码我们可以看出,其实string类型实际上是由char[]实现的,一个string的诞生绝不像我们想想的那样简单,要由栈、堆同时配合,才会有一个string的诞生。这一点在第四节会有介绍。
  当然,当字符串驻留机制无效时,我们可以很简便的使用string.Intern方法将其手动驻留至字符串池中,例如以下代码:
    string a = "a";
    string b = new string('a',1);    
    Response.Write(ReferenceEquals(a, string.Intern(b)));

    输出:True

  程序返回Ture,说明变量"a"与"b"来自同一内存地址。

  好了,下面两节将通过实例为大家展示string的内部秘密,大家可以通过它测试一下自己对string的了解程度,敬请期待!

转自:http://www.cnblogs.com/isline/archive/2009/02/04/1383799.html

.Net Discovery 系列之一--string从入门到精通(上)的更多相关文章

  1. .Net Discovery 系列之二--string从入门到精通(下)

    前两节我们介绍了string的两个基本特性,如果你觉得你已经比较全面的了解了string,那么就来看看这第3.4两节吧. 三.有趣的比较操作  在第一节与第二节中,我们分别介绍了字符串的恒定性与与驻留 ...

  2. .Net Discovery 系列之五--深入浅出.Net实时编译机制(上)

    欢迎阅读“.Net Discovery 系列”文章,本文将分上.下两部分为大家讲解.Net JIT方面的知识,敬请雅正. JIT(Just In Time简称JIT)是.Net边运行边编译的一种机制, ...

  3. .Net Discovery系列之三 深入理解.Net垃圾收集机制(上)

    前言: 组成.Net平台一个很重要的部分----垃圾收集器(Garbage Collection),今天我们就来讲讲它.想想看没有GC,.Net还能称之为一个平台吗?各种语言虽然都被编译成MSIL,但 ...

  4. .Net Discovery系列之十二-深入理解平台机制与性能影响(下)

    上一篇文章中Aicken为大家介绍了.Net平台的垃圾回收机制.即时编译机制与其对性能的影响,这一篇中将继续为大家介绍.Net平台的异常捕获机制与字符串驻留机制. 三.关于异常捕获机制 虽然我们已经很 ...

  5. .Net Discovery 系列之七--深入理解.Net垃圾收集机制(拾贝篇)

    关于.Net垃圾收集器(Garbage Collection),Aicken已经在“.Net Discovery 系列”文章中有2篇的涉及,这一篇文章是对上2篇文章的补充,关于“.Net Discov ...

  6. kibana从入门到精通-Kibana安装

    作者其他ELK快速入门系列文章 Elasticsearch从入门到精通 logstash快速入门实战指南 简介 Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之 ...

  7. NHibernate从入门到精通系列

    http://www.cnblogs.com/GoodHelper/archive/2011/02/17/1948744.html NHibernate从入门到精通系列(4)——持久对象的生命周期(上 ...

  8. ShoneSharp语言(S#)的设计和使用介绍系列(4)— 入门概述

    ShoneSharp语言(S#)的设计和使用介绍 系列(4)- 入门概述 作者:Shone 声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp. ...

  9. Mybatis系列(一)入门

    Mybatis系列(一)入门 mybatis简介 MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结 ...

随机推荐

  1. MVC常用特性使用

    简介 在以前的文章中,我和大家讨论如何用SingalR和数据库通知来完成一个消息监控应用. 在上一篇文章中,我介绍了如何在MVC中对MongoDB进行CRUD操作. 今天,我将继续介绍一些在开发中非常 ...

  2. python psutil监控系统资源【转】

    通过 运用 Python 第三方 系统 基础 模块, 可以 轻松 获取 服务 关键 运营 指标 数据,包括 Linux 基本 性能. 块 设备. 网卡 接口. 系统 信息. 网络 地址 库 等 信息. ...

  3. 环境变量GOBIN导致GoClipse运行出现异常

    Windows 10家庭中文版,go version go1.11 windows/amd64, Eclipse IDE for C/C++ Developers Photon Release (4. ...

  4. Coursera台大机器学习技法课程笔记14-Radial Basis Function Network

    将Radial Basis Function与Network相结合.实际上衡量两个点的相似性:距离越近,值越大. 将神经元换为与距离有关的函数,就是RBF Network: 可以用kernel和RBF ...

  5. opencv的级联分类器(mac)

    级联分类器的介绍:级联分类器训练 因为要训练负样本,windows电脑有些问题,所以就只能有mac进行训练. 在windows中训练,准备了负样本之后,进行三步. 1.opencv_createsam ...

  6. Java Map 接口

    Map接口中键和值一一映射. 可以通过键来获取值. 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值. 当访问的值不存在的时候,方法就会抛出一个NoSuchEl ...

  7. [转] HTML 获取屏幕、浏览器、页面的高度宽度

    本篇主要介绍Web环境中屏幕.浏览器及页面的高度.宽度信息. 目录 1. 介绍:介绍页面的容器(屏幕.浏览器及页面).物理尺寸与分辨率.展示等内容. 2. 屏幕信息:介绍屏幕尺寸信息:如:屏幕.软件可 ...

  8. 【AtCoder】ARC092

    C - 2D Plane 2N Points 把能连边的点找到然后跑二分图匹配即可 #include <bits/stdc++.h> #define fi first #define se ...

  9. MongoDB 安全配置

    前言 随着MongoDB使用人群企业越来越广泛,黑客的注意力也转移到了其中.比如去年很火热的MongoDB劫持事件,很多人对MongoDB的安全也越来越重视.今天,我们就简单总结一些MongoDB的安 ...

  10. Python3 turtle安装和使用教程

    Python3 turtle安装和使用教程   Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数 ...