摘要:string是编程中使用最频繁的类型。一个string表示一个恒定不变的字符序列集合。string类型直接继承自object,故他是一个引用类型,也就是说线程的堆栈上不会有任何字符串(直接继承自object的类型一定是引用类型,因为所有的值类型都继承自System.ValueType。值得指出的是System.ValueType是引用类型)。

string是编程中使用最频繁的类型。一个string表示一个恒定不变的字符序列集合。string类型直接继承自object,故他是一个引用类型,也就是说线程的堆栈上不会有任何字符串(直接继承自object的类型一定是引用类型,因为所有的值类型都继承自System.ValueType。值得指出的是System.ValueType是引用类型)。

一、创建字符串

   C#将string认为是基元类型,也就是说编译器也许在源代码中用文本常量来直接表达字符串。编译器会将这些文本常量字符串存放在托管模块的元数据中。在C#中创建string。

  不能通过new操作符来创建string

 class Program
{
static void Main(string[] args)
{
string hap = new string("heaiping");
}
}

以上代码会出现编译错误。

相反,应该使用下面的简化语法

  class Program
{
static void Main(string[] args)
{
string hap ="heaiping";
}
}

为什么呢,请看下面代码。

代码

namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s = "heaiping";//不使用new
SomeClass sc = new SomeClass();//使用new
}
} class SomeClass
{
}
}

编译以上代码查看ILDasm生成的IL代码如下。

代码

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// 代码大小 14 (0xe)
.maxstack 1
.locals init ([0] string s,
[1] class StringStudy.SomeClass sc)
IL_0000: nop
IL_0001: ldstr "heaiping"//此处创建string
IL_0006: stloc.0
IL_0007: newobj instance void StringStudy.SomeClass::.ctor()//此处创建SomeClass
IL_000c: stloc.1
IL_000d: ret
} // end of method Program::Main

  如上所见C#对于对象实例的IL指令为newobj,然后调用其构造函数,而对于string,它用了一个特殊的指令ldstr(是不是loadstring,呵呵),该指令通过从元数据中获取文本常量来构造string对象,这表明,CLR有一个更为高效特殊的字符串构造方式。

  string类型也通过了通过new来构造而不是通过文本元数据来构造的构造器,可以接受char*等参数,书上说是为了托管C++提供的,这个就不研究了~~~~~~~

  看下面的代码

  class Program
{
static void Main(string[] args)
{
string s = "heaiping";
s = s + " " + "soft";
}
}

  上面的代码使用了+操作符来连接字符串,书上建议不要使用+,因为它会在要执行垃圾回收器的托管堆上创建多个字符串,不是单纯的附在后面。应该使用System.Text.StringBuilder(下次学习);

  还有一种特殊的声明方式,允许编译器将引号之间的字符都认为是字符串的一部分。

代码

   class Program
{
static void Main(string[] args)
{
//fileA和fileB是一样的
string fileA = "C://Windows//Temp";
string fileB = @"C:/Windows/Temp";
}
}

@符号会爆速编译器该字符串为一个字面字符串,告知编译器将反斜杠看为字符而不是转义字符。

二、字符串的恒定性

  字符串的恒定性,就是说,一个字符串一旦被创建,就不可能将其变长、变短、或者改变其中任何的字符。

三、字符串比较

罗列一下

成员 成员类型 描述
Compare 静态方法 按照字符串的排序比较,可以控制语言文化,以及是否考虑大小写
CompareTo 实例方法 按照字符串的排序比较,内部使用了当前线程的语言文化
StartsWith/EndsWith 实例方法 是否以指定的字符串开头或者结尾,大小写敏感,内部使用了当前线程的语言文化
CompareOrdinal 静态方法 按照字符集比较,不考虑语言文化,大小写敏感,比较快
Eauals 静态/实例 静态方法比较字符集,实例方法内部调用CompareOrdinal,静态方法首先会检查两个引用是否指向同一个对象,如果是则不再比较字符集
GetHashCode 实例方法 返回列散码

 除以上,string类型还承载了==和!=操作符,其方法内部都是调用string的静态Equals方法实现的 

 对于以上比较有以下说明:

  • 如果是判断字符串是否相等,用CompareOridinal,因为它仅仅是比较字符集是否相等,比较快。
  • 如果是逻辑比较,呈现给用户,何为逻辑比较,虽然有些字符串的字符集不一样,但是逻辑上确实是等的,应该使用Compare方法,Compare内部使用了特定语言文化的排序表。

  在字符串的比较时,语言文化对其影响是很大,Compare方法在内部首先获取与调用线程相关的CurrentCulture,然后读取CurrentCulture的CompareInfo属性。因为CompareInfo对象封装了一个和语言文化相关联的字符比较表,所以每一种文化仅有一个CompareInfo对象。

四、字符串驻留

  字符床的比较是很浪费性能的,CLR通过字符串驻留(string interning)机制来提高性能,看下面代码:

代码

namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s = "hap";
Console.WriteLine(object.ReferenceEquals("hap",s)); SomeClass sc = new SomeClass();
Console.WriteLine(object.ReferenceEquals(new SomeClass(), sc));
}
} class SomeClass
{ }
}

按照引用类型的特性,上面的两个输出都应该为False,因为都是不同的引用在比较,可是  Console.WriteLine(object.ReferenceEquals("hap",s));却输出为True,为什么,难道string不是引用类型吗?? string当然是引用类型,上面的表现由CLR对于string的特殊处理决定的:

  当CLR初始化的时候,它会创建一个内部的散列表,键为字符串,值为指向托管堆中字符串对象的引用。刚开始,该表为空。当JIT编译器编译方法时,它会在散列表中查找每一个文本常量字符串。对于上面的关于string的代码,编译器会首先查找“hap”字符串,并且因为没有找到(第一次还没有),它便就在托管堆上面构造一个新的string对象(指向hap),然后将hap字符串和指向该对象的引用添加到散列表(此时散列表中已经存在键为hap的值),接着JIT编译器在编译器中查找第二个hap字符串,当然此时散列表中已经存在hap,所以不会执行任何操作,代码开始执行。

  当代码执行时,它会在第一行发现需要hap字符串的引用。于是,CLR便在其内部的散列表中查找hap,并且会找到它,这样指向先前创建的string对象的引用就被保存在变量s中。当再往下执行,CLR会再一次在其内部散列表中查找hap,并且仍然会找到,这样子,指向同一个string对象的引用会被传递给object的ReferenceEquals作为第一个参数,当然和第二个参数是同一个引用,自然比较结果为True。

  继续看下面代码:

代码

namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s1 = "hap";
string s2 = "h";
string s3 = s2 + "ap";
string s4 = "h" + "ap";
Console.WriteLine(object.ReferenceEquals(s1,s3));//false
Console.WriteLine(s1.Equals(s3));//true Console.WriteLine(object.ReferenceEquals(s1, s4));//true }
}
}

这次的输出搞的怪怪的,心里有点毛,到底是怎么回事?原来当一个引用字符串的方法被JIT编译时,所有嵌入在源代码中的文本常量总会被添加到散列表中,s2引用的字符串(h)和一个文本常量字符串(ap)被连接在一起。结果是一个新构造的、位于托管堆中有s3引用的字符串对象,这个动态创建的字符串包含的是一个hap,但是它没有被添加到CLR散列表中,而是另起炉灶,不同的引用所有ReferenceEquals返回fasle,调用Equals相等是因为他们有相同的字符集。

  至于s4=”h"+"ap",是因为IL指令会将两个文本常量字符串连接为一个文本常量字符串hap,所有输出为True。

  ReferenceEquals方法在比较时不需要逐个字符的去比较,只比较引用,效率明显要高于Equals,如果将程序中所有的字符串比较都用引用而不是字符集,那么系统的性能将得到很大的提高。如果有一个方法要是能将含有相同字符集的动态字符串变为托管堆中的一个字符串对象的话,应用程序需要的对象也将更少,从而可以提升系统性能。非常幸运。string类型提高了两个静态方法将可以做到这一点。

public static string Intern(string str);
public static string IsInterned(string str);

  第一个方法Intern接受一个string参数,然后在CLR内部散列表中查找它。如果能够找到该字符串,Intern将返回已经存在的引用。如果找不到,该字符串将被添加到散列表中,Intern最后也会返回它的引用。如果引用程序不再保存作为参数传递的string的引用,垃圾回收器将会回收它。对上面的程序用Intern重新修改:

代码

namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s1 = "hap";
string s2 = "h";
string s3 = s2 + "ap";
s3 = string.Intern(s3);
Console.WriteLine(object.ReferenceEquals(s1,s3));//true
Console.WriteLine(s1.Equals(s3));//true
}
}
}

输出全部为true,神奇啊。因为Intern也是需要执行时间的,所以书上建议只有需要多次比较同一个字符串时,才 应该使用字符串驻留技术,否则得不偿失。

  需要注意的是,垃圾回收器不会释放CLR内部散列表中引用的字符串对象。只有当应用程序域都不再应用这些字符串对象时,他们才会被释放。

  IsInterned方法与Intern方法的不同之处是如果在散列表中找不到将会返回null,而不是创建。

代码

namespace StringStudy
{
class Program
{
static void Main(string[] args)
{
string s1 = "heaiping";
string s2 = "h";
string s3 = s2 + "ap";
s3 = string.IsInterned(s3);
if (s3 == null)
{
Console.WriteLine(0);
}
else
{
Console.WriteLine(1);
} }
}
}

上面程序输出0,因为就当前程序来说散列表中只存在heaiping而不存在hap,所以最后s3变为null。

string还有一些成员Length,IndexOF...Copy...等等。。。不再说了,累了啊。

  

对了以前发过一个关于string的小组讨论http://home.cnblogs.com/group/topic/38270.html

原文如下:

class Class1 

    static void StrChange(string str) 
    { 
      str = "hellow"; 
    }

static void Main() 
    { 
      string str = "123";//申明一个字符串 
      StrChange(str);//调用方法 
      Console.WriteLine(str);//输出字符串 
    } 
}

输出的结果是 "123" 
string 到底是值类型还是引用类型? 
如果是值类型,结果倒还说的过去.但是我记得string 是引用类型啊...难道是我记错了?? 
如果是引用类型的话.输出的结果应该是: "hellow" 
请问这是为什么啊?? 大家帮忙解释一下..谢谢

现在明白了,其实就是与CLR对于string的这个散列表有关,

至于string是特殊的引用类型,个人感觉特殊就特殊在这个散列表上吧

C#文本处理(String)学习笔记的更多相关文章

  1. Java的string学习笔记 与char数组和bufferstring的比较

    ---恢复内容开始--- 一直用的C 导致这种类望而生畏 现在终于鼓起勇气学习一下 首先学习string类型 String s1 = "AbCdEf"; String s2 = & ...

  2. zepto源码--核心方法5(文本操作)--学习笔记

    涉及到文本内容的主要有三个函数:html, text, val. 我们已经见过多次,一个函数多种用途的情况,今天这三个函数也不例外,既可以获取内容,也可以设置内容.判断条件就是有没有传入参数,如果没有 ...

  3. Java String学习笔记

    参照:https://www.jianshu.com/p/2f209af80f84 常量池: Java代码被编译成class文件时,会生成一个常量池(Constant pool)的数据结构,用以保存字 ...

  4. Python学习笔记基础篇——总览

    Python初识与简介[开篇] Python学习笔记——基础篇[第一周]——变量与赋值.用户交互.条件判断.循环控制.数据类型.文本操作 Python学习笔记——基础篇[第二周]——解释器.字符串.列 ...

  5. Linux学习笔记(四) vi编辑器

    一.vi 编辑器 vi 编辑器 (Visual Interface) 是所有 Unix 及 Linux 系统下标准的编辑器,相当于 Windows 系统中的记事本 它有三种模式,分别是: Comman ...

  6. Python 3之str类型、string模块学习笔记

    Windows 10家庭中文版,Python 3.6.4, Python 3.7官文: Text Sequence Type — str string — Common string operatio ...

  7. canvas学习笔记(中篇) -- canvas入门教程-- 颜色/透明度/渐变色/线宽/线条样式/虚线/文本/阴影/图片/像素处理

    [中篇] -- 建议学习时间4小时  课程共(上中下)三篇 此笔记是我初次接触canvas的时候的学习笔记,这次特意整理为博客供大家入门学习,几乎涵盖了canvas所有的基础知识,并且有众多练习案例, ...

  8. 机器学习框架ML.NET学习笔记【3】文本特征分析

    一.要解决的问题 问题:常常一些单位或组织召开会议时需要录入会议记录,我们需要通过机器学习对用户输入的文本内容进行自动评判,合格或不合格.(同样的问题还类似垃圾短信检测.工作日志质量分析等.) 处理思 ...

  9. Flutter学习笔记(11)--文本组件、图标及按钮组件

    如需转载,请注明出处:Flutter学习笔记(10)--容器组件.图片组件 文本组件 文本组件(text)负责显示文本和定义显示样式,下表为text常见属性 Text组件属性及描述 属性名 类型 默认 ...

  10. JavaSE学习笔记(5)---内部类和String类

    JavaSE学习笔记(5)---内部类和String类 一.内部类基础 转自菜鸟教程 ​ 在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.广泛意义上的内部类一般来 ...

随机推荐

  1. Sql2008的行列转换之行转列

    今天在工作的时候遇到了行列转换的问题,记得去年有一段时间经常写,但是许久不用已经记不太得了.好记性不如烂笔头,忙完之后赶紧记录一下. 关键字:PIVOT(行转列),UNPIVOT(列转行) 先说说 P ...

  2. 【JAVA错误笔记】 - 【Could not open ServletContext resource [/WEB-INF/applicationContext.xml]解决方法】

    错误描述: Could not open ServletContext resource [/WEB-INF/applicationContext.xml] 原因分析: 问题主要由于加载spring的 ...

  3. Office 365 for TechNet 版本国家地区没有中国选项

    TechNet订阅用户提供一个Office365的注册,今天想测试一下,结果发现TechNet版本的没有中文地区选项 以下是官网E3使用账号注册,有中国地区 以下为TechNet订阅的office36 ...

  4. 自动构建工具Ant的使用-笔记

    第一:什么是Ant? Apache Ant是一个基于Java的生成工具.据最初的创始人James Duncan Davidson的介绍,这个工具的名称是another neat tool(另一个整洁的 ...

  5. JDBC实现往MySQL插入百万级数据

    想往某个表中插入几百万条数据做下测试, 原先的想法,直接写个循环10W次随便插入点数据试试吧,好吧,我真的很天真.... DROP PROCEDURE IF EXISTS proc_initData; ...

  6. linux采用模块方法,添加一个新的设备

    该文转载自:http://rangercyh.blog.51cto.com/1444712/521244 系统调用是操作系统内核和应用程序之间的接口,而设备驱动程序是操作系统内核和机器硬件之间的接口. ...

  7. sass 入门教程

    1.引言 众所周知css并不能算是一们真正意义上的“编程”语言,它本身无法未完成像其它编程语言一样的嵌套.继承.设置变量等工作.为了解决css的不足,开发者们想到了编写一种对css进行预处理的“中间语 ...

  8. fork();

    僵死进程: 父进程没有等待子进程,wait() 子进程会变成僵死进程. int main(int arg, char *args[]){ pid_t pid = fork();//调用fork产生一个 ...

  9. docker无法连接进程

    simon@simon-X550LD:~$ docker run hello-world docker: Cannot connect to the Docker daemon. Is the doc ...

  10. 大量字段表单在PHP便捷处理分享

    关于程序开发中的表单批量提交策略很多时候一个表单太多的字段,如何能够高效获取表单字段,也为如何提神开发的效率和统一性? 比如一个系统的某个有26个字段,那么我用表单的名称用26个a到z的字母, 你是选 ...