今天下午朋友讨论组上讨论一个关于string的问题,问题是这样的,string a="aaa";string b=a;a="bbb",为什么测试b的值不改变?之前我看过一个文章,知道肯定不相等,因为引用地址的一系列问题,但是不能很好的解释于同事听,所以几经查阅资料,在博客园里找到一篇文章,解决了我的疑问,同时也解决了关于C#中"=="与equals的计算结果与别的语言不一致的问题。在此转载过来,以备巩固。(以下为转载内容,转载地址http://terrylee.cnblogs.com/archive/2005/12/26/304876.html
概述

String在任何语言中,都有它的特殊性,在.NET中也是如此。它属于基本数据类型,也是基本数据类型中唯一的引用类型。字符串可以声明为常量,但是它却放在了堆中。希望通过本文能够使大家对.NET中的String有一个深入的了解。

不可改变对象

在.NET中String是不可改变对象,一旦创建了一个String对象并为它赋值,它就不可能再改变,也就是你不可能改变一个字符串的值。这句话初听起来似乎有些不可思议,大家也许马上会想到字符串的连接操作,我们不也可以改变字符串吗?看下面这段代码:

using System;

namespace Demo1
{
/// <summary>
/// String连接测试
/// </summary>
public class Test
{
public static void Main(string[] args)
{
string a = "";
Console.WriteLine(a); a += "";
Console.WriteLine(a);
Console.ReadLine();
}
}
}

运行的结果:

1234

12345678

看起来我们似乎已经把MyStr的值从“1234”改为了“12345678”。事实是这样的吗?实际上并没有改变。在第5行代码中创建了一个String对象它的值是“1234”,MyStr指向了它在内存中的地址;第七行代码中创建了一个新的String对象它的值是“12345678”,MyStr指向了新的内存地址。这时在堆中其实存在着两个字符串对象,尽管我们只引用了它们中的一个,但是字符串“1234”仍然在内存中驻留。

引用类型

前面说过String是引用类型,这就是如果我们创建很多个相同值的字符串对象,它在内存中的指向地址应该是一样的。也就是说,当我们创建了字符串对象a,它的值是“1234”,当我们再创建一个值为“1234”的字符串对象b时它不会再去分配一块内存空间,而是直接指向了a在内存中的地址。这样可以确保内存的有效利用。看下面的代码:

using System;

namespace Demo2
{
/// <summary>
/// String引用类型测试
/// </summary>
public class Test
{
public static void Main(string[] args)
{
string a = ""; Console.WriteLine(a); Test.Change(a); Console.WriteLine(a);
Console.ReadLine();
} public static void Change(string s)
{
s = "";
}
}
}

运行结果:

1234

1234

做一个小改动,注意Change(ref string s)

using System;

namespace Demo2
{
/// <summary>
/// String引用类型测试
/// </summary>
public class Test
{
public static void Main(string[] args)
{
string a = ""; Console.WriteLine(a); Test.Change(ref a); Console.WriteLine(a);
Console.ReadLine();
} public static void Change(ref string s)
{
s = "";
}
}
}

运行结果:

1234

5678

字符串的比较

在.NET中,对字符串的比较操作并不仅仅是简单的比较二者的值,= =操作首先比较两个字符串的引用,如果引用相同,就直接返回True;如果不同再去比较它们的值。所以如果两个值相同的字符串的比较相对于引用相同的字符串的比较要慢,中间多了一步判断引用是否相同。看下面这段代码:

using System;

namespace Demo3
{
/// <summary>
/// String类型的比较
/// </summary>
public class Test
{
public static void Main(string[] args)
{
string a = "";
string b = "";
string c = "";
c += ""; int times = ;
int start,end; ///测试引用相同所用的实际时间
start = Environment.TickCount;
for(int i=;i<times;i++)
{
if(a==b)
{}
}
end = Environment.TickCount;
Console.WriteLine((end-start)); ///测试引用不同而值相同所用的实际时间
start = Environment.TickCount;
for(int i=;i<times;i++)
{
if(a==c)
{}
}
end = Environment.TickCount;
Console.WriteLine((end-start)); Console.ReadLine();
}
}
}

执行的结果(运行的结果可能有些不同):

1671

4172
由此我们看出值相同时的比较用= =比引用相同时的比较慢了好多。这里仅仅是一个测试,因为做这样的比较并没有任何实际的意义。
有一点需要明确的是,.NET中==跟Equals()内部机制完全是一样的,==是它的一个重载。

public static bool operator ==(string a, string b)
{
return string.Equals(a, b);
} public static bool Equals(string a, string b)
{
if (a == b)
{
return true;
}
if ((a != null) && (b != null))
{
return a.Equals(b);
}
return false;
}

字符串驻留

看一下这段代码:

using System;

namespace Demo4
{
/// <summary>
/// String的驻留
/// </summary>
public class Test
{
public static void Main(string[] args)
{
string a = "";
string s = "";
s += ""; string b = s;
string c = String.Intern(s); Console.WriteLine((object)a == (object)b);
Console.WriteLine((object)a == (object)c);
Console.ReadLine();
}
}
}

执行的结果:

False

True
在这段代码中,比较这两个对象发现它的引用并不是一样的。如果要想是它们的引用相同,可以用Intern()函数来进行字符串的驻留(如果有这样的值存在)。

StringBuilder对象

通过上面的分析可以看出,String类型在做字符串的连接操作时,效率是相当低的,并且由于每做一个连接操作,都会在内存中创建一个新的对象,占用了大量的内存空间。这样就引出StringBuilder对象,StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。这一点我们平时使用中也许都知道,连接操作频繁的时候,使用StringBuilder对象。但是这两者之间的差别到底有多大呢?来做一个测试:

using System;
using System.Text; namespace Demo5
{
/// <summary>
/// String和StringBulider比较
/// </summary>
public class Test
{
public static void Main(string[] args)
{
string a = "";
StringBuilder s = new StringBuilder(); int times = ;
int start,end; ///测试String所用的时间
start = Environment.TickCount;
for(int i=;i<times;i++)
{
a += i.ToString();
}
end = Environment.TickCount;
Console.WriteLine((end-start)); ///测试StringBuilder所用的时间
start = Environment.TickCount;
for(int i=;i<times;i++)
{
s.Append(i.ToString());
}
end = Environment.TickCount;
Console.WriteLine((end-start)); Console.ReadLine();
}
}
}

运行结果:

884

0

通过上面的分析,可以看出用String来做字符串的连接时效率非常低,但并不是所任何情况下都要用StringBuilder,当我们连接很少的字符串时可以用String,但当做大量的或频繁的字符串连接操作时,就一定要用StringBuilder。

关于string,我今天科普的的更多相关文章

  1. 科普:String hashCode 方法为什么选择数字31作为乘子

    1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主 ...

  2. 科普:为什么 String hashCode 方法选择数字31作为乘子

    作者:coolblog 此文章转载自:https://segmentfault.com/a/1190000010799123 1. 背景 某天,我在写代码的时候,无意中点开了 String hashC ...

  3. Lua的string和string库总结

    Lua有7种数据类型,分别是nil.boolean.number.string.table.function.userdata.这里我总结一下Lua的string类型和string库,复习一下,以便加 ...

  4. (letcode)String to Integer (atoi)

    Implement atoi to convert a string to an integer. Hint: Carefully consider all possible input cases. ...

  5. 【转】科普Spark,Spark是什么,如何使用Spark

    本博文是转自如下链接,为了方便自己查阅学习和他人交流.感谢原博主的提供! http://www.aboutyun.com/thread-6849-1-1.html http://www.aboutyu ...

  6. repeater绑定泛型list<string>

    菜鸟D重出江湖,依然是菜鸟,囧!言归正传—— 工作中遇到一个repeater绑定的问题,数据源是一个list<string> 集合,然后在界面上使用<%#Eval()%>绑定. ...

  7. java基础(五) String性质深入解析

    引言   本文将讲解String的几个性质. 一.String的不可变性   对于初学者来说,很容易误认为String对象是可以改变的,特别是+链接时,对象似乎真的改变了.然而,String对象一经创 ...

  8. String hashCode 方法为什么选择数字31作为乘子

    1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法.然后大致看了一下 hashCode 的实现,发现并不是很复杂.但是我从源码中发现了一个奇怪的数字,也就是本文的主 ...

  9. JDK源码学习笔记——String

    1.学习jdk源码,从以下几个方面入手: 类定义(继承,实现接口等) 全局变量 方法 内部类 2.hashCode private int hash; public int hashCode() { ...

随机推荐

  1. synchronized和ReentrantLock

    一.什么是sychronized sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类. 在修饰代码块的时候需要一个reference对象作为锁的对象. 在修饰方法的时候默 ...

  2. maven+springmvc+spring+mybatis+velocity整合

      一.ssmm简介 ssmm是当下企业最常用的开发框架架构 maven:管理项目jar包,构建项目 spring:IOC容器,事务管理 springmvc:mvc框架 myBatis:持久层框架 v ...

  3. yii2 配置文件加载顺序, 以及调用自定义配置信息。

    在配置上一般有以下问题: 每个团队成员都会有自己的配置项,提交这样的配置项将会影响其他团队成员: 生产库密码和API密钥不应该出现在代码库中: 有多个服务器环境的情况:开发.测试.生产:每一种环境应该 ...

  4. 基于任务的异步模式(TAP)

    Task .net 4.0为我们带来了Task的异步,我们有以下三种方法创建Task. 1,Task.Factory.StartNew,比较常用. 2,Task.Run,是.net 4.5中增加的. ...

  5. Spring整合Ehcache管理缓存(转)

    目录 前言 概述 安装 Ehcache的使用 HelloWorld范例 Ehcache基本操作 创建CacheManager 添加缓存 删除缓存 实现基本缓存操作 缓存配置 xml方式 API方式 S ...

  6. Listview中显示不同的视图布局

    import java.util.ArrayList; import android.app.Activity;import android.content.Context;import androi ...

  7. 浅谈对ionic项目的理解

    在思考怎么将客户端app连接到服务器的时候,就在想ionic项目的本质是什么,一开始因为ionic serve这一命令,我以为它自己就是个服务器,但是后来一细想又感觉不是这样,不然客户端又该怎么和服务 ...

  8. maven添加自己的jar包到本地仓库

     mvn install:install-file -DgroupId=com.esotericsoftware -DartifactId=minlog -Dversion=1.2 -Dpackagi ...

  9. Add listitem with javascript 分类: Sharepoint 2015-07-16 20:23 4人阅读 评论(0) 收藏

    SP.SOD.executeFunc('sp.js', 'SP.ClientContext', createListItem);//makes sure sp.js is loaded and the ...

  10. 关于Erlang中的behaviour

    唔,听说过这四个牛逼渣渣的behaviour:gen_server,gen_fsm,gen_event,supervisor.所以也就更加好奇behaviour的实现. 在解释它是怎么工作的之前,我们 ...