值类型 引用类型

值类型表示存储在栈上的类型,包括简单类型(int、long、double、short)、枚举、struct定义;

引用类型表示存在堆上的类型,包括数组、接口、委托、class定义;

string 是引用类型

字符特殊性

  • 不可变性。字符串创建后,重新赋值的话,不会更新原有值,而是将引用地址更新到一个新的内存地址上。

  • 留存性。.NET运行时有个字符串常量池的概念,在编译时,会将程序集中所有字符串定义集中到一个内存池中,新定义的字符串会优先去常量池中查看是否已存在,如果存在,则直接引用已存在的字符串,否则会去堆上重新申请内存创建一个字符串。

    下面是关于字符串的一些单元测试,仔细观察下各个不同:

      [Fact]
    public void Base_Test()
    {
    string a = "abc";
    string b = "abc";
    //字符串的留存性,初始化后会放入常量池,b直接引用a的对象
    Assert.True(string.ReferenceEquals(a, b)); string c = new String("abc");
    string d = new String("abc");
    //直接new的话,会重新分配内存
    Assert.False(string.ReferenceEquals(c, d));
    Assert.False(string.ReferenceEquals(a, c)); string e = "abc";
    //这里e还是使用字符串的留存性,且使用的还是a的地址。证明c分配的内存引用并没有放入常量池替换
    Assert.True(string.ReferenceEquals(a, e));
    Assert.False(string.ReferenceEquals(c, e)); string f = "abc" + "abc";
    string g = a + b;
    string h = "abcabc";
    //f在编译期间确定,实际还是从常量池中获取
    //IsInterned 表示从常量池中获取对应的字符串,获取失败返回null
    //a+b实际上是发生了字符串组合运算,内部重新new了一个新的字符串,所以f,g引用地址不同
    Assert.False(string.ReferenceEquals(f, g));
    Assert.True(string.ReferenceEquals(string.IsInterned(f), h));
    Assert.True(string.ReferenceEquals(f, h));
    }

Stringbuilder

字符串拼接是一个非常耗资源的操作,例如 string a="b"+"c" ,实际上创建了3个字符串"b"、"c"、"bc"。所以在这个时候就需要StringBuilder来专门执行字符串拼接操作了。

那么StringBuilder是如何实现的呢?

实际上StringBuilder内部维护了一个char数组,所有的appned类的操作都是将字符串转化为char存入数组。最后ToString()的时候才去组装string,减少了大量中间string的创建,是非常高效的字符串组装工具。

StringBuilder内部还有一个 Capacity 属性,用于定义数组的初始容量,默认值为25。超过容量会触发扩容操作。所以在实际操作中,如果我们能预估到拼接字符串的长度,在定义StringBuilder给 Capacity 属性附上一个合理的值,将会有更加高效的性能。

equals ==

  • equals:比较字符串的值
  • ==:比较字符串的引用地址是否相同

首先有个前提,我们所看到的equals,==,来自于System.Object对象,几乎所有的原生对象都对其进行了重写,才构成了我们目前的认知。重写equals必须重写GetHashCode。官方给出重写的实现约定如下:

Equals每个实现都必须遵循以下约定:

  • 自反性(Reflexive): x.equals(x)必须返回true.
  • 对称性(Symmetric): x.equals(y)为true时,y.equals(x)也为true.
  • 传递性(Transitive): 对于任何非null的应用值x,y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)必须返回true.
  • 一致性(Consistence): 如果多次将对象与另一个对象比较,结果始终相同.只要未修改x和y的应用对象,x.equals(y)连续调用x.equals(y)返回相同的值l.
  • 非null(Non-null): 如果x不是null,y为null,则x.equals(y)必须为false

GetHashCode:

  • 两个相等对象根据equals方法比较时相等,那么这两个对象中任意一个对象的hashcode方法都必须产生同样的整数。
  • 在我们未对对象进行修改时,多次调用hashcode使用返回同一个整数.在同一个应用程序中多次执行,每次执行返回的整数可以不一致.
  • 如果两个对象根据equals方法比较不相等时,那么调用这两个对象中任意一个对象的hashcode方法,不一同的整数。但不同的对象,产生不同整数,有可能提高散列表的性能.

请慎重重写Equals和GetHashCode!!重写Equals方法必须要重写GetHashCode!!

关于equals方法参数 StringComparison

public enum StringComparison
{
//
// 摘要:
// 使用区分区域性的排序规则和当前区域性比较字符串。
CurrentCulture = 0,
//
// 摘要:
// 通过使用区分区域性的排序规则、当前区域性,并忽略所比较的字符串的大小写,来比较字符串。
CurrentCultureIgnoreCase = 1,
//
// 摘要:
// 使用区分区域性的排序规则和固定区域性比较字符串。
InvariantCulture = 2,
//
// 摘要:
// 通过使用区分区域性的排序规则、固定区域性,并忽略所比较的字符串的大小写,来比较字符串。
InvariantCultureIgnoreCase = 3,
//
// 摘要:
// 使用序号(二进制)排序规则比较字符串。
Ordinal = 4,
//
// 摘要:
// 通过使用序号(二进制)区分区域性的排序规则并忽略所比较的字符串的大小写,来比较字符串。
OrdinalIgnoreCase = 5
}

通常情况下最好使用 Ordinal或者OrdinalIgnoreCase,性能上最为高效。

除非有特殊的需要,不要使用 InvariantCulture或者InvariantCultureIgnoreCase,因为它要考虑所有Culture的字符转化对比情况,性能是极差的。

CurrentCulture和CurrentCultureIgnoreCase由于只有本地Culture对比,所以性能还可以接受。

参数传递

首先关于参数的存储,参数是存在栈上的。传递参数时,会将对象的“值”在栈copy一份,然后将副本的值传给方法。对象参数的传递分为两种 “值传递”和“引用传递”。(注意这里的引号

  • 值传递。默认的参数传递都是这种方式。会将对象的值在栈copy一份,然后将复制集的值传给方法。这里的值对于 值类型来说,即为对象副本的值。对于引用类型来说,即为对象在堆上的地址。
  • 引用传递。可以通过 ref out 关键字实现。对于值类型,会直接传入原对象在栈上的引用。对于引用类型,会传入原有对象的堆地址的引用。

这里string虽然是引用类型,但是产生的效果缺和值类型参数传递一样的。大家参考上面关于string的特性思考下原因。

静心慢慢回味下列单元测试

    [Fact]
public void Base_Test()
{
//引用类型参数
TestClass s = new TestClass();
s.Tag = "abc"; TestMethod m = new TestMethod();
m.ReNew(s);
//参数s 实际是对象 s的 地址拷贝。两者在栈上不同,但是指向的堆地址相同
//在ReNew方法中 "参数s" 重新指向了一个新的对象,但是不影响旧的对象s
Assert.True(string.Equals("abc", s.Tag)); m.Change(s, "123");
//Change方法是直接修改 参数s 指向的堆对象内的字段数据,所有对象s字段也发生了变化
Assert.True(string.Equals("123", s.Tag)); m.ReNew2(ref s);
//注意和ReNew的区别,因为是ref 引用传递,所有原对象引用地址指向了新new的对象地址
Assert.False(string.Equals("abc", s.Tag));
Assert.True(string.Equals("cba", s.Tag)); //值类型参数
int val = 100;
//Change方法内部改变了val的值,但不影响val原来的值
m.Change(val);
Assert.True(val == 100); m.Change(out val);
//使用out标记,改变了val原来的值
Assert.True(val == 123);
}
} public class TestMethod
{
public void ReNew(TestClass c)
{
c = new TestClass() { Tag = "cba" };
} public void ReNew2(ref TestClass c)
{
c = new TestClass() { Tag = "cba" };
} public void Change(TestClass c, string tag)
{
c.Tag = tag;
} public void Change(int a)
{
a = 123;
}
public void Change(out int a)
{
a = 123;
}
} public class TestClass
{
public string Tag { get; set; }
}

ref out

ref out都是用来标识通过引用传递方式传参。不同的是,ref 需要参数在方法调用前初始化,out 则要求参数在方法体内赋值。

装箱 拆箱

装箱,即值类型转化为引用类型;从内存存储角度,将值类型从栈的值copy,然后放到堆上,并附加额外的引用类型功能内存占用(如类型指针、同步块索引等)。

拆箱,即引用类型转化为值类型。从内存存储角度,获取引用类型的指针,得到值copy,放到栈上。

从性能角度上,装箱的性能损耗>拆箱的性能损耗。在实际运用中,我们要尽量避免装箱和拆箱,这也是泛型类型出现后,一个非常大的作用就是避免了装箱拆箱的大量操作。

从String类型发散想到的一些东西的更多相关文章

  1. 【原创】自制string类型(已完成)

    这篇文章首发于360doc http://www.360doc.com/content/21/0526/17/73755266_979099504.shtml ,其实360doc里面的那个也是我的帐号 ...

  2. List<Integer>里有可能存String类型元素吗?

    这其实是我遇到的一个线上bug,在这里分享给大家. 如果是用反射,那就很简单了,毕竟泛型只是在编译期进行约束,对运行期是无能为力的. 想想看,如果不使用反射,有没有办法做到呢? 问题起因 在我们公司的 ...

  3. C#string类型总结

    字符串的特性:不可变性,每对字符串做拼接或者重新赋值之类的操作,都会在内存中产生一个新的实例.  所以说,在.Net平台下,如果你对一个字符串进行大量的拼接赋值等操作,会产生大量的垃圾.    --- ...

  4. 把《c++ primer》读薄(3-1 标准库string类型初探)

    督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 问题1:养成一个好习惯,在头文件中只定义确实需要的东西 using namespace std; //建议需要什么再using声 ...

  5. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

  6. 浅谈String类型

    首先,我们要知道的是String类型是一个引用类型,它的基类是Object.并且它的内容是只读的. 我们有时候经常会看到两个字符串类型,一个是“Sting”,一个是“string”.大写的String ...

  7. c++ string类型转换为char *类型

    string 是c++标准库里面其中一个,封装了对字符串的操作 把string转换为char* 有3中方法: 1.data 如: string str="abc"; char *p ...

  8. java中关于String 类型数据 的存储方式

    Constant Pool常量池的概念: 在讲到String的一些特殊情况时,总会提到String Pool或者Constant Pool,但是我想很多人都不太 明白Constant Pool到底是个 ...

  9. 关于SpringMVC中如何把查询数据全转成String类型

    之前,本想与客户商量做几张固定的报表予使用,结果发现客户每个月都需要各种各样的报表,所以我们做了个窗口用于直接执行SQL语句:数据量一开始并不是很大查询出来的数据较少(约1-6W左右),所以刚开始几个 ...

随机推荐

  1. PHP curl_multi_setopt函数

    (PHP 5 >= 5.5.0) curl_multi_setopt — 设置一个批处理cURL传输选项. 说明 bool curl_multi_setopt ( resource $mh , ...

  2. Redis服务之常用数据类型

    上一篇博客我们聊了下redis的主从复制.aof持久化.集群.慢日志相关配置指令的说明,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13416534.html ...

  3. LeetCode 164. Maximum Gap[翻译]

    164. Maximum Gap 164. 最大间隔 Given an unsorted array, find the maximum difference between the successi ...

  4. 无版号的ios手游下架 TF签名才是正确选择?

    2020年8月1日开始,无版号的ios手游就要全部下架appstore了,在这样关键的时刻,TF签名成了ios手游的最后一根救命稻草.因为被下架或者根本无法通过appstore的上架审核,ios手游的 ...

  5. TF签名是什么?比企业签名好在哪里?

      现在苹果企业签名的服务大致分为三类,苹果企业签名.超级签名和TF签名,而TF签名TF签名又称 TestFlight 签名,是目前最稳定的签名方式. ​   「优势」   关键词:零风险;限制少;安 ...

  6. 使用opencv在Qt控件上播放mp4文件

    文章目录 简介 核心代码 运行结果 简介 opencv是一个开源计算机视觉库,功能非常多,这里简单介绍一下OpenCV解码播放Mp4文件,并将图像显示到Qt的QLabel上面. 核心代码 头文件 #i ...

  7. 002_centos7关闭防火墙

    防火墙是比较烦人的,在自己做实验,或者实际应用中,如果配置不好的话,会出现各种匪夷所思的问题,那么如何关闭呢 在centos7里,防火墙改为了firewalld进程 首先用命令firewall-cmd ...

  8. 准确率、精确率、召回率、F1

    在搭建一个AI模型或者是机器学习模型的时候怎么去评估模型,比如我们前期讲的利用朴素贝叶斯算法做的垃圾邮件分类算法,我们如何取评估它.我们需要一套完整的评估方法对我们的模型进行正确的评估,如果模型效果比 ...

  9. Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练

    Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练 目录 Alink漫谈(十七) :Word2Vec源码分析 之 迭代训练 0x00 摘要 0x01 前文回顾 1.1 上文总体流程图 1 ...

  10. docker找回构建时被删除的文件

    设想这样一个场景:当一个docker镜像被多次引用构建,在某次构建中某个文件被删除,如何找回被删除的文件? 要想回答这么一个问题,首先得熟悉下docker镜像的分层存储结构,镜像每一层都是只读的: 那 ...