“大菜”:源于自己刚踏入猿途混沌时起,自我感觉不是一般的菜,因而得名“大菜”,于自身共勉。


扩展阅读:深入理解值类型和引用类型

基本概念

string(严格来说应该是System.String) 类型是我们日常coding中用的最多的类型之一。那什么是String呢?^ ~ ^

String是一个不可变的连续16位的Unicode代码值的集合,它直接派生自System.Object类型。

与之对应的还有一个不常用的安全字符串类型System.Security.SecureString,它会在非托管的内存上分配,以便避开GC的黑手。主要用于安全性特高的场景。[具体可查看msdn这里不展开讨论了。=>msdn查看详情

特性

  1. 由于String类型直接派生于Object,所以它是引用类型,那就意味着String对象的实例总是存在于堆上。
  2. String具有不变性,也就是说一旦初始化,它的值将永远不变。
  3. String类型是封闭的,换言之,你的任何类型不能继承String。
  4. 定义字符串实例的关键字string只是System.String 类型的一个映射。

注意事项

  1. 关于字符串中的回车符和换行符一般大家喜欢直接硬编码‘\r\n’,但是不建议这么做,一旦程序迁移到其他平台,将出现错误。相反,推荐使用System.Environment类的NewLine属性来生成回车符和换行符,可以跨平台使用的。
  2. 常量字符串的拼接和非常量字符串在CLR中行为是不一样的。具体请查看性能部分。

  3. 字符串之前加@符号会改变编译器的行为,如果加了@符号,编译器会把String中的转义字符视为正常字符来显示。也就是我定义的什么内容就是什么内容,主要在使用文件路径或者目录字符串中使用。以下两个String内容的输出将完全一致。
        static void Main(string[] args){string a = "c:\\temp\\1";string b = @"c:\temp\1";Console.WriteLine(a);Console.WriteLine(b);Console.Read();           }  

性能

  1. c#的编译器直接支持String类型,并将定义的常量字符串在编译期直接存放到模块的元数据中。然后会在运行时直接加载。这也说明String类型的常量在运行时是有特殊待遇的。
  2. 由于字符串的不变性,也就意味着多个线程同时操作该字符串不会有任何线程安全的问题。这在某些共享配置的设计中很有用。
  3. 如果程序经常会对比重复度比较高的字符串,这会造成性能上的影响,因为对比字符串是要经过几个步骤的。为此CLR引入了一个字符串重用的技术,学名叫做‘字符串留用’。原理就是:CLR会在初始化的时候创建一个内部的哈希表,key是字符串,value就是留用字符串在托管堆上的引用。
    String类型提供了两个静态方法来操作这个哈希表:

String.Intern

String.IsInterned

具体请查看msdn(https://msdn.microsoft.com/zh-cn/library/system.string.isinterned(v=vs.110).aspx)
但是c#编译器默认是不开启字符串留用功能的,因为如果程序大量把字符串留用,应用程序总体性能可能会变得更慢。(微软也是挺纠结的,程序员TMD的更纠结)

  1. 如果我们的程序中有很多个一模一样值的常量字符串, c#的编译器会在编译期间把这些字符串合并为一个并写入模块的元数据中,然后修改所有引用该字符串的代码。这也是一种字符串重用技术,学名‘字符串池’。这意味着什么呢?这意味着所有值相同的常量字符串其实引用的是同一个内存地址的实例,在相同值非常多的情况下能显著提高性能和节省大量内存。
string s1 = "hello 大菜";
string s2 = "hello 大菜";
unsafe
{fixed (char* p = s1){Console.WriteLine("字符串地址= 0x{0:x}", (int)p);}fixed (char* p = s2){Console.WriteLine("字符串地址= 0x{0:x}", (int)p);}
} 

输出结果:

字符串地址= 0x80002d84
字符串地址= 0x80002d84

可见实例的值只分配了一次,但是有一点需要说明,字符串仅用于编译期能确定值的字符串,也就是常量字符串。如果我的程序修改为:

args = new string[] { "dfasfdsa"};
string s1 = "hello 大菜"+ args[0];
string s2 = "hello 大菜"+args[0];
unsafe
{fixed (char* p = s1){Console.WriteLine("字符串地址= 0x{0:x}", (int)p);}fixed (char* p = s2){Console.WriteLine("字符串地址= 0x{0:x}", (int)p);}
}

运行结果:

字符串地址= 0x2e3c
字符串地址= 0x2e7c
  1. 平时coding避免不了字符串的连接,如果一个频繁拼接字符串的场景下使用‘+’,对程序整体性能和GC影响还是挺大的,为此c#推出了 StringBuilder类型来优化字符串的拼接。相对于String类型的不变性来说,StringBuilder更像是可变的字符串类型。它的底层数据结构是一个Char的数组。另外还有容量(默认为16),最大容量(默认为int.MaxValue)等属性。StringBuilder的优势在于字符总数未超过‘容量’的时候,底层数组不会重新分配,这和String每次都重新分配形成最大的对比。如果字符总数超过‘容量’,StringBuilder会自动倍增容量属性,用一个新的数组来容纳原来的值,原来数组将会被GC回收。可见如果StringBuilder频繁的动态扩容也会损害性能,但是影响可能会比String小的多。 合理的设置StringBuilder初始容量对程序有很大帮助。测试如下:
int count = 100000;
Stopwatch sw = new Stopwatch();
sw.Start();
string s = "";
for (int i = 0; i < count; i++){s += i.ToString();}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

运行结果:

14221

查看GC的情况

Gc执行的是如此频繁。 性能是可想而知的。接着看一下StringBuilder

int count = 100000;
Stopwatch sw = new Stopwatch();
sw.Start();
StringBuilder sb = new StringBuilder();//听说程序员都这样命名StringBuilder
for (int i = 0; i < count; i++){sb.Append(i.ToString());
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

运行结果:

12

GC情况:

几乎没有GC(可能还未达到触发GC的临界点),如果我合理初始化了StringBuilder 容量,生产环境中结果差距将会更大。 呵呵 ^ ~ ^

其他

关于字符串留用和字符串池

  1. 一个程序集加载的时候,CLR默认会留用该程序集元数据中描述的所有文本常量字符串。由于可能会出现额外的哈希表查找造成的性能下降的现象,所以现在可以禁用这个特性了。
  2. coding中我们平常比较两个字符串是否相等,那这个过程是怎么样的呢?
    1. 首先判断字符的数量是否相等。
    2. CLR逐个对比字符最终确定是否相等。

这个场景是适合字符串留用的。因为不再需要经过以上的两个步骤,直接哈希表拿到value就可以对比确定了。

关于字符串拼接性能

基于以上所有知识,那是不是StringBuilder拼接字符串性能永远都高于符号‘+’呢?答案是否定的。

 static void Main(string[] args){int count = 10000000;Stopwatch sw = new Stopwatch();sw.Start();            string str1 = "str1", str2 = "str2", str3 = "str3";for (int i = 0; i < count; i++){string s = str1 + str2 + str3;}sw.Stop();Console.WriteLine($@"+用时: {sw.ElapsedMilliseconds}" );sw.Reset();sw.Start();for (int i = 0; i < count; i++){StringBuilder sb = new StringBuilder();//听说程序员都这样命名StringBuildersb.Append(str1).Append(str2).Append(str3);}sw.Stop();Console.WriteLine($@"StringBuilder.Append 用时: {sw.ElapsedMilliseconds}");Console.Read();}

运行结果:

+用时: 553
StringBuilder.Append 用时: 975

符号‘+’最终会调用String.Concat方法,当同时连接几个字符串时,并不是每连接一个都分配一次内存,而是把几个字符都作为 String.Concat方法的参数,只分配一次内存。所以在拼接的字符串个数比较少的场景下,String.Concat 性能是略高于StringBuilder.Append。string.Format 方法最终调用的是StringBuilder,这里不做展开讨论了,请自行参考其他文档。

所以万事都不是绝对的!!每个事物都有适合自己的场景,我们都需要自己去探索。(程序员太累了

以上都是非生产环境测试结果,如果错误,请及时指正

请尊重一个猿的辛苦,转载请标明出处 ^ ~ ^ 。部分图片来源于网络,如有侵权请及时联系。让我们一起进步吧

一个不止于IT圈内容的微信公众号,欢迎关注,交流更多的IT知识。不定时会有惊喜奥 ^ ~ ^

转载于:https://www.cnblogs.com/zhanlang/p/9612521.html

c#基础系列2---深入理解 String相关推荐

  1. Java基础提升篇:理解String 及 String.intern() 在实际中的应用

    点击上方"好好学java",选择"置顶公众号" 优秀学习资源.干货第一时间送达! 好好学java java知识分享/学习资源免费分享 关注 精彩内容 你所需要的 ...

  2. Java基础系列【导读】

    Java基础系列导读 Java基础 JaveWEB Java基础 基础系列[一]-- Java语言介绍 基础系列[二]-- 环境搭建 基础系列[三]-- Java基础语法 基础系列[四]-- 面向对象 ...

  3. [转载] 夯实Java基础系列8:深入理解Java内部类及其实现原理

    参考链接: Java内部类 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tut ...

  4. 夯实Java基础系列9:深入理解Class类和Object类

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  5. 夯实Java基础系列11:深入理解Java中的回调机制

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  6. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  7. 夯实Java基础系列18:深入理解Java内部类及其实现原理

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  8. 夯实Java基础系列23:深入理解Java继承、封装、多态的底层实现原理

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  9. string substring的用法_夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战...

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  10. 夯实Java基础系列14:深入理解Java枚举类

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

最新文章

  1. 曾经流行的,即将流行的几种渲染模式(render pipeline)
  2. 创建文件夹(如果已经存在就清空)python脚本
  3. java 权重_java实现权重随机算法
  4. sqoop将hive导出到mysql_Sqoop hive导出到mysql[转]
  5. 连续不等_第九讲 函数的连续性与函数的间断点
  6. CSS 水平垂直居中
  7. IIS Express 使用详细说明
  8. 在实践中总结出的几条 CodeReview 实用经验
  9. [原创]消灭eclipse中运行启动的错误:“找不到或无法加载主类”问题
  10. web开发需要注意/n的问题
  11. 计算机显卡发展史,外置显卡发展简史
  12. apkg格式怎么打开_jpg怎么转换成pdf?再不学就晚了
  13. HashMap原理详解:探测技术(Probing)、数据聚集(Clustering)、寻址方式(Addressing)、墓碑删除(tombstones)等技术的深度剖析
  14. 【Bootstrap】选择折叠项collapse
  15. Codeup-2044:神奇的口袋
  16. QT中使用C++ socket通信,socket通信原理三次握手和四次握手详解、客户端与服务端实例详解
  17. 大风车音乐计算机,《大风车》音乐教学
  18. Android 增强版百分比布局库 为了适配而扩展
  19. R语言ggplot2可视化:使用patchwork包将两个ggplot2可视化结果图像垂直堆叠排列进行组合构图(vertically stack the plots)
  20. 东南亚(Lazada shoppe)自养号测评如何解决收货地址及ip问题详解

热门文章

  1. uwsgi模式_uWSGI / Pylons的开发模式(重新加载新代码)
  2. odbc mysql server_使用MySQL ODBC进行MySQL和SQL Server转换
  3. InnoDB 行格式
  4. Mysql 第一范式入门
  5. Spark数据倾斜是如何造成的
  6. Opencv学习笔记_计算机视觉是什么?Opencv的起源
  7. ubuntu安装cairo
  8. (原創) 开启Notebook时,建议从天版中间开启 (日記)
  9. 协议栈处理中的conntrack HASH查找/Bloom过滤/CACHE查找/大包与小包/分层处理风格
  10. Spring Annotation是怎么工作的?