关于string

string是一种很特殊的数据类型,它既是基元类型又是引用类型,在编译以及运行时,.Net都对它做了一些优化工作。  一.恒定的字符串
  要想比较全面的了解stirng类型,首先要清楚.Net中的值类型与引用类型。在C#中,以下数据类型为值类型:
    bool、byte、char、enum、sbyte以及数字类型(包括可空类型)
  以下数据类型为引用类型:
    class、interface、delegate、object、stirng
  被声明为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 a = "str_1";
    string b = "str_1";
    Response.Write(a.Equals(b));
    Response.Write(ReferenceEquals(a,b));

    输出:True (Equals比较字符串对象的值)
          True (ReferenceEquals比较字符串对象的引用,由于字符串驻留机制,a与b的引用相同)

  代码二:
            string a = "str_1str_2";
            string b = "str_1";
            string c = "str_2";
            string d = b + c;
            Response.Write(a.Equals(d));
    Response.Write(ReferenceEquals(a, d));

    输出:True(Equals比较字符串对象的值)
          False(ReferenceEquals比较字符串对象的引用,由于变量d的值为变量连接的结果,字符串驻留机制无效)

  代码三:
    string a = "str_1str_2";
    string b = "str_1" + "str_2";
    Response.Write(a.Equals(b));
    Response.Write(ReferenceEquals(a, b));

    输出:True(Equals比较字符串对象的值)
          True (ReferenceEquals比较字符串对象的引用,由于变量b的值为常量连接的结果,字符串驻留机制有效。如果变量b的值由“常量+变量”的方式得出,则字符串驻留无效)

  代码四:
    string a = "str_1";
    string b = String.Copy(a);
    Response.Write(a.Equals(b));
    Response.Write(ReferenceEquals(a, b));

    输出:True(Equals比较字符串对象的值)
          False (ReferenceEquals比较字符串对象的引用,Copy操作产生了新的string对象)

  代码五:
    string a = "str_1";
    string b = String.Copy(a);
    b = String.Intern(b);
    Response.Write(a.Equals(b));
    Response.Write(ReferenceEquals(a, b));

    输出:True(Equals比较字符串对象的值)
          True (ReferenceEquals比较字符串对象的引用,String.Intern实现了字符串驻留)

  代码六:
    string a = "str_1";
    string b = String.Copy(a);
    string c = "str_1";
    Response.Write((object)a == (object)b);
    Response.Write((object)a == (object)c);

    输出:False (“==”在两边为引用类型时,则比较引用的地址,所以a与b为    不同引用)
          True (ReferenceEquals比较字符串对象的引用,a与c由于字符串驻留机制,引用相同)

  代码七:
    string a = "str_1";
    string c = "str_1";
    Response.Write(a == c);

    输出:True
  刚才我们提到过,“==”在两边为引用类型时,则比较引用的地址;如果是值类型时则比较值。string为引用类型,那么上面的代码是比较了变量a与c的地址还是值呢?答案是:比较了值!因为在string类型比较的时候,“==”已经被重载为“Equals”了,所以,虽然你在用“==”比较两个引用类型,但实际上是在用“Equals”比较它们的值!

  代码八:
    string a = "a";
    string b = new string('a', 1);
    Response.Write(a.Equals(b));
    Response.Write(ReferenceEquals(a, b));

    输出:True (Equals比较值,a与b的值相同)
          False (ReferenceEquals比较字符串对象的引用)

  代码九:
    string a = "a";
    string b = new string('a', 1);
    Response.Write(a.Equals(string.Intern(b)));
    Response.Write(ReferenceEquals(a, string.Intern(b)));
    输出:True (Equals比较值,无论是否Intern都会相同)
          True (ReferenceEquals比较字符串对象的引用,Intern已经将b驻留至字符串池内)

  代码十:
    string a = "str";
    string b = "str_2".Substring(0,3);
    Response.Write(a.Equals(b));
    Response.Write(ReferenceEquals(a, b));
    输出:True (Equals比较值,a与c的值相同)
          False (ReferenceEquals比较字符串对象的引用,Substring操作产生了新的字符串对象)
      此段代码产生了3个string对象,是哪3个呢?如果你不明白,还是从头再看一遍吧!


四.常见问题
    1.“string = ”与“new stirng()”的区别
    string test = "a";
    string test = new string('a', 1);

  以上两行代码的效果是一样的,它们的区别在于加载”a”的时间不同:第一行的“a”是一个常量,在编译期就已经被放在一个叫做常量池的地方了,常量池通常装载一些在编译期被确定下来的数据,例如类、接口等等;而第二行是运行时CLR在堆中生成的值为“a”的字符串对象,所以后者没有字符串驻留。
  2. string 与 String的区别
  String的大名叫做System.String,在编译为IL代码时,string和System.String会生成完全相同的代码:(ps:long和System.Int64,float和System.Single等也有此特性)
  C#代码:
    string str_test = "test";
           System.String Str_test = "test";
  IL码:
      // 代码大小       14 (0xe)
      .maxstack  1
      .locals init ([0] string str_test,
               [1] string Str_test)
      IL_0000:  nop
      IL_0001:  ldstr      "test"
      IL_0006:  stloc.0
      IL_0007:  ldstr      "test"
      IL_000c:  stloc.1
      IL_000d:  ret
    所以,二者的区别并不在于底层,而是在于string是类似于int的基元类型;System. String是框架类库(FCL)的基本类型,二者之间有直接的对应关系。
  3.StringBuilder
  StringBuilder提供了高效创建字符串的方法,由StringBuilder表示的字符串是可变的(非恒定的),在需要多处使用“+”连接字符串变量的时候,推荐使用StringBuilder来完成,最后调用其ToString()方法输出。当调用了StringBuilder的ToString()方法之后,StringBuilder将返回其内部维护的一个字符串字段引用,如再次修改StringBuilder,它将会创建一个新的字符串,这时被修改的是新的字符串,原来已经返回的字符串才不会发生改变。
    StringBuilder有两个比较重要的内部字段,大家需要掌握:
    m_MaxCapacity:StringBuilder的最大容量,它规定了最多可以放置到        m_StringValue的字符个数,默认值为Int32.MaxValue。m_MaxCapacity一旦被指定就不能再更改。
    m_StringValue:StringBuilder维护的一个字符数组串,实际上可以理解为一个字符串。StringBuilder重写的Tostring()方法返回的就是这个字段。

转载于:https://www.cnblogs.com/yidianfeng/archive/2009/02/05/1384469.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/299291.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python第七天--字符串的方法与注释

capitalize() 把字符串的第一个字符改为大写casefold() 把整个字符串的所有字符改为小写center(width) 将字符串居中,并使用空格填充至长度width的新字符串count(sub[,start[,end]]) 返回sub在字符串里边出现的次数,start和end参数表示范围&#xff…

android的热点分配ip_Android 得到连接热点的ip的方法

下面给大家介绍Android 得到连接热点的ip的方法 ,具体代码如下所示:WifiManager wifiManager (WifiManager) this.getSystemService(Context.WIFI_SERVICE);if (!wifiManager.isWifiEnabled()) {System.out.println("");wifiManager.setWifiE…

【SQL基础】T-SQL函数类型——元数据函数

一般来说,元数据函数返回的是有关指定数据库和数据对象的信息。下面介绍几种元数据函数。 1、COL_NAME(tab_id,col_id),返回表tab_id的tab_id的列名。 例:selectCOL_NAME(OBJECT_ID(employees),3); 结果为’FirstName’ 2、COLUMNPROPERTY(id…

为什么我们总是忍不住要刷微信?

全世界只有3.14 % 的人关注了爆炸吧知识真正决定人与人之间的差距的,其实是我们对事物的见识与内心的格局,见识的深浅决定人生的深浅,格局的大小决定了人生之路是宽是窄。今天给大家推荐几个有深度、有想法的公众号,希望能够给你带…

22、多进程和多线程

 Android进程简介 Android会启动一个LINUX进程和一个主线程。默认的情况下,所 有该程序的组件都将在该进程中运行。当启动应用程序时,Linux会为每 一个程序单独分配一个进程,该进程默认只拥有一个主线程。 组件可以运行在当前进程中,也可以运行在其他进程中。组件运行在 哪个…

Docker小白到实战之常用命令演示,通俗易懂

前言上一篇大概认识了Docker,主要是从概念、架构、优点及流程方面进行阐述,并进行安装和体验;接下来就开始进行实操学习,在演示过程中会针对关键的知识点进行归纳和总结,这里先从常用命令说起,来吧&#xf…

lumen 配置数据库结果自动转数组_lumen 数据库操作 Cannot use object of type stdClass as array...

你可能记得,操作数据库时$dataList 什么什么 ->get();能查询到数据 数据结果为数组加stdClass类型你使用 $dataList $dataList->toArray();进行数据转化结果你发现,没有正确转化,你再到里面去转化,但结果证明也是徒劳foreach ($allDa…

今天这个日子,大多数人都不知道…

全世界只有3.14 % 的人关注了爆炸吧知识我是蝙蝠,身体虽小五毒俱全你确定要尝吗?我是果子狸,人们一度“谈我色变”但如今又被端上餐桌!我是野兔,人们说我很可爱我的肉却变成“野味”毛发变成皮草……今天,世…

PostgreSQL表的行数统计

PostgreSQL表的行数统计 在很多情况下我们需要知道一个表的记录数有多少。如果你发现你有这样的需求,你还应该问问这样的统计的精确度到底又多高。如果你在做会计报表,你需要非常的精确。如果你做一个网页的记数器,可能有一些误差也是允许的…

SharePoint at .NET技术大会

在这个月上海举行的.NET技术大会上,我将奉献一节名为《基于SharePoint的Web应用开发模型》的课程。由于大会的定位是“面向企业级.NET开发深度应用”,我相信大家想听的一定不是单纯的介绍SharePoint Server,或是怎么做一个Web Part。思来想去…

对DDD的常见误区

这里是Z哥的个人公众号每周五11:45 按时送达当然了,也会时不时加个餐~我的第「205」篇原创敬上大家好,我是Z哥。我从 2014 年开始接触 DDD 到现在也有 7 年多时间了,在这个期间踩过很多坑,也是自己慢慢从充…

Python办公自动化Day2-openpyxl

目录 文章声明⭐⭐⭐让我们开始今天的学习吧!常规操作添加数据遍历所有单元格数据合并/取消合并单元格添加/删除行与列移动指定范围单元格 文章声明⭐⭐⭐ 该文章为我(有编程语言基础,非编程小白)的 Python办公自动化自学笔记知识…

vsftpd服务

实验1VSFTPD实验环境在虚拟机Linux 6.5系统下需要2台Linux系统一台A作为服务端一条B作为测试客户端开启2台Linux系统。实验目标A作为服务端配置VSFTPD服务器实现FTP服务。B作为测试客户端验证服务器A的共享是否有效。配置真实主机确保真实主机能ping通2台虚拟机。实验步骤1. 首…

传递函数_使用python计算麦克风阵列信号的传递函数

使用python写了一个测试麦克风阵列传递函数的demo,有需要的自取。该代码使用了第三方库ThinkDSP。1. 传递函数首先解释下什么是传递函数:把具有线性特性的对象的输入与输出间的关系,用一个函数(输出波形的拉普拉斯变换与输入波形的拉普拉斯变…

LNK2005 连接错误解决办法

nafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2YAPAXIZ) 已经在 LIBCMTD.lib(new.obj) 中定义nafxcwd.lib(afxmem.obj) : error LNK2005: "void __cdecl operator delete(void *)" (??3YAXPAXZ) 已经在 …

中国数学竞赛史上最玩命的“赌徒”,为了国家荣誉,他不惜用生命换来了五次世界第一...

全世界只有3.14 % 的人关注了爆炸吧知识知识君今天,要跟大家介绍的是,北大历史上首位被授予博士学位的人,深受癌症折磨的同时还带领着中国学子连续5次站上世界顶端的人,他的故事,他的品格,值得被所有人记住…

33条C#、.Net经典面试题目及答案[zt]

本文集中了多条常见的C#、.Net经典面试题目例如“.NET中类和结构的区别”、“ASP.NET页面之间传递值的几种方式?”,并简明扼要的给出了答案,希望能对学习C#、.Net的读者有所帮助。 1, 请你说说.NET中类和结构的区别? 答:结构和类…

控制用户的访问之权限、角色【weber出品必属精品】

权限的作用 限制用户对数据的访问 权限的分类 1. 系统权限:能够存取数据库的权限 2. 对象权限:操作数据库对象的内容 系统权限 1.1 如何创建用户: SQL> create user test default tablespace users identified by test;用户已创建。 1…

javq接口_Java为什么要使用接口_java接口怎么使用

Java接口是什么Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。接口(英语:Interface)&am…

[(转)hystar整理]Entity Framework 教程

预备知识 2 LINQ技术 2 LINQ技术的基础 - C#3.0 2 自动属性 2 隐式类型 2 对象初始化器与集合初始化器 3 匿名类 3 扩展方法 4 Lambda表达式 4 .NET中的数据访问 4 DataSet方案 5 改进的的DataSet方案 5 手写代码通过ADO.NET2…