一、深入理解字符串的不可变特性

string可以看做是char的只读数组。char c = s[1]

C#中字符串有一个重要的特性:不可变性,字符串一旦声明就不再可以改变。所以只能通过索引来读取指定位置的char,不能对指定位置的char进行修改。如果要对char进行修改,那么就必须创建一个新的字符串,用s. ToCharArray()方法得到字符串的char数组,对数组进行修改后,调用new string(char[])这个构造函数来创建char数组的字符串。一旦字符串被创建,那么char数组的修改也不会造成字符串的变化。

常见疑问:“谁说字符串不可变?string s1 = "abc";s1="123"s1这不是变了吗”,要区分变量名和变量指向的值的区别:程序中可以有很多字符串,然后由字符串变量指向他们,变量可以指向其他的字符串,但是字符串本身没有变化。字符串不可变性指的是内存中的字符串不可变,而不是变量不变
string s2 = s1;//s2指向s1指向的字符串,而不是s2指向s1,哪怕s1以后指向了其他内存,那么s2还是指向"123"

内存分配分析:

string s1 = "abc";这句代码执行完之后,在栈内存中开辟一块内存空间存储字符串变量s1,在堆内存中开辟一块内存空间来存储字符串对象“abc”,字符串变量s1指向字符串对象“abc”,如下图所示:

s1="123";这句代码执行完之后,在堆内存中再开辟一块内存空间来存储字符串对象“123”,字符串变量s1指向字符串对象“123”,如下图所示:

此时可以看到,字符串变量s1已经指向了新的字符串对象“123”了,字符串对象“abc”已经没有任何的字符串变量指向它了,此时字符串对象“abc”就再也没有用了,当对象一定不再有用的时候GC(垃圾收集器)就可以将对象回收了,所以GC会在一个合适的时机来回收字符串对象“abc”,释放其占用的内存资源。判断一个对象是否一定不再有用的标准就是没有任何的变量指向它

string s2 = s1; 这句代码执行完之后,在栈内存中开辟一块内存空间存储字符串变量s2,字符串变量s2也指向字符串对象“123”

例子:将字符串中的l替换为i

 string s = "hell world!";//字符数组 只读的 不能对其 进行赋值
char[] chars = s.ToCharArray();//得到一个字符数组
chars[]='i';//修改字符数组的值
s=new string(chars);//生成一个新的字符串
Console.WriteLine(s);
for (int i = ; i < s.Length; i++)
{
Console.WriteLine(s[i]);
}
Console.ReadKey();

  因为字符串是不可变的,所以CLR(CLR是公共语言运行时,Common Language Runtime)可能会将相同值的字符串用同一个实例。程序中大量使用字符串,有不少是重复性的,为了降低内存占用,.Net将代码中声明的字符串放到字符串拘留池中,值相同的字符串共享同一个实例。字符串是不变的。不是所有字符串都在拘留池中,.Net会判断哪些该放。

  object.ReferenceEquals方法判断两个变量是不是一个实例(同一个对象)

 string s1 = "abc";
string s2 = "abc";
Console.WriteLine(object.ReferenceEquals(s1,s2));

动态字符串默认是不在字符串拘留池中的

字符串拘留池测试程序:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace 字符串拘留池
{
class Program
{
static void Main(string[] args)
{
//重点1
//因为有字符串拘留池的存在所以下面的程序有一个string对象
//string s1 = "孤傲苍狼";
//string s2 = "孤傲苍狼";
string s1 = "孤傲苍狼";
string s2 = "孤傲苍狼";
if (s1 == s2)//这里比较的是s1和s2的内容是不是一样
{
Console.WriteLine("s1==s2的结果是:{0}", s1 == s2);
}
//上面的代码产生一个字符串对象
Console.WriteLine("object.ReferenceEquals(s1,s2)的结果是:{0}",object.ReferenceEquals(s1,s2));//这里判断的是s1和s2所指向的对象是不是同一个
Console.WriteLine("================================================");
string s3 = "yzk";
string s4 = "yzk";
string s5 = new string(s1.ToCharArray());
//object.ReferenceEquals方法判断两个变量是不是一个实例(同一个对象)
Console.WriteLine("object.ReferenceEquals(s3, s4)的结果是:{0}",object.ReferenceEquals(s3, s4));//true
Console.WriteLine("object.ReferenceEquals(s3, s5)的结果是:{0}", object.ReferenceEquals(s3, s5));//false
Console.WriteLine("object.ReferenceEquals(s4, s5)的结果是:{0}", object.ReferenceEquals(s4, s5));//false
Console.WriteLine("================================================");
string s6 = "abc";
string s7 = "ab"+"c";
Console.WriteLine("object.ReferenceEquals(s6, s7)的结果是:{0}", object.ReferenceEquals(s6, s7));//true
//默认只有代码中写的字面才会进入字符串拘留池,动态字符串默认是不在字符串拘留池中的
string str1 = "abc";//abc就是代码中写的字面
string a1 = Console.ReadLine();
string a2 = Console.ReadLine();
string str2 = a1 + a2;//str2是动态算出来的字符串,
Console.WriteLine("str1==str2的结果是:{0}",str1==str2);//结果为true
Console.WriteLine("object.ReferenceEquals(str1, str2)的结果是:{0}", object.ReferenceEquals(str1, str2));//结果为false
//重点2
//new就一定会创建一个新的对象
//两个string对象
string str3 = new string("abc".ToCharArray());
string str4 = "abc";
//==是判断内容是否相等
Console.WriteLine("str3==str4的结果是:{0}", str3 == str4);//结果为true
Console.WriteLine("object.ReferenceEquals(str3, str4)的结果是:{0}", object.ReferenceEquals(str3, str4));//结果为false
Console.WriteLine("================================================");
Point p1 = new Point(, );
Point p2 = new Point(, );
//==是判断内容是否相等
Console.WriteLine("p1 == p2的结果是:{0}",p1 == p2);
//object.ReferenceEquals判断是否是同一个对象
Console.WriteLine("object.ReferenceEquals(p1, p2)的结果是:{0}", object.ReferenceEquals(p1, p2));
Console.WriteLine("================================================");
Console.ReadKey();
}
}
}

程序运行结果:

  string是不变的,因此每次运算都会重新创建一个string对象,例如:s=s+”abc”;,此时将会创建一个新的字符串对象,s指向了新创建的字符串对象

 string s1=”a”;
string s2=”b”;
string s3=”c”;
string s4=s1+s2+s3;//s1+s2产生"ab","ab"+s3产生"abc",所以产生两个字符串

  大量的字符串相连会产生大量的中间字符串,字符串是对象,对象的产生是慢的,而且会占用大量的内存。所以要避免大量的字符串的连接操作。

  例如:在for循环中拼接字符串

 for(int i=;i<=;i++)
{
s=s+i.ToString();
}

  每循环一次,就进行一次字符串拼接运算,那么就会创建一个新的字符串,循环1000次,就会创建1000个字符串,就需要开辟1000块内存空间来存储这些中间字符串,这是非常暂用内存资源的。

  对象的创建是非常慢和消耗资源的,因此对于需要动态拼接字符串的场合(比如创建SQL语句)用StringBuilder来代替string,StringBuilder内部实现了字符串拼接不会有string的缺陷。

.Net高级技术——字符串拘留池(Intern)的更多相关文章

  1. 第八篇 .NET高级技术之字符串暂存池(缓冲池)

    字符串不可变性,字符串的‘暂存池’两个特性 字符串是引用类型,程序中会存在大量的字符串对象,如果每次都创建一个字符串对象,会比较浪费内存.性能低,因此CLR做了“暂存池”(拘留池,缓冲池,暂存池),在 ...

  2. 字符串常量池和String.intern()方法在jdk1.6、1.7、1.8中的变化

    字符串常量池也是运行时常量池 jdk1.6中,它是在方法区中,属于“永久代” jdk1.7中,它被移除方法区,放在java堆中 jdk1.8中,取消了“永久代”,将常量池放在元空间,与堆独立了 pub ...

  3. 常量池之字符串常量池String.intern()

    运行时常量池是方法区(PermGen)的一部分. 需要提前了解: 1. JVM内存模型. 2. JAVA对象在JVM中内存分配 常量池的好处 常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现 ...

  4. 对于JVM中方法区,永久代,元空间以及字符串常量池的迁移和string.intern方法

    在Java虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表. 在过去(当自定义类加载器使用不普 ...

  5. Knowledge Point 20180309 字符串常量池与String,intern()

    引言 什么都先不说,先看下面这个引入的例子: public static void test4(){ String str1 = new String("SEU") + new S ...

  6. 理解Java字符串常量池与intern()方法

    String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel" + "lo ...

  7. 结合字符串常量池/String.intern()/String Table来谈一下你对java中String的理解

    1.字符串常量池 每创建一个字符串常量,JVM会首先检查字符串常量池,如果字符串已经在常量池中存在,那么就返回常量池中的实例引用.如果字符串不在池中,就会实例化一个字符串放到字符串池中.常量池提高了J ...

  8. String+、intern()、字符串常量池

    字符串连接符 "+"及字符串常量池实验.字符串final属性 结果预览 public class StrTest{ public static void main(String[] ...

  9. 从HotSpot VM源码看字符串常量池(StringTable)和intern()方法

    引言 字符串常量池(StringTable)是JVM中一个重要的结构,它有助于避免重复创建相同内容的String对象.那么StringTable是怎么实现的?"把字符串加入到字符串常量池中& ...

随机推荐

  1. winform框架源码-Devexpress开发框架

    链接: https://pan.baidu.com/s/1TnDj6qftGEUl3sTB8QXs_w 提取码: 关注公众号[GitHubCN]回复获取   开发模式:C/S C/S采用的是dev14 ...

  2. MySQL慢查询优化

    MySQL数据库是常见的两个瓶颈是CPU和I/O的瓶颈,CPU在饱和的时候一般发生在大量数据进行比对或聚合时.磁盘I/O瓶颈发生在装入数据远大于内存容量的时候,如果应用分布在网络上,那么查询量相当大的 ...

  3. day7 面向对象进阶

    面向对象高级语法部分 通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例 ...

  4. 【LOJ】 #2025. 「JLOI / SHOI2016」方

    题解 有什么LNOI啊,最后都是JLOI罢了 一道非常--懵逼的统计题 当然是容斥,所有的方案 - 至少有一个点坏掉的正方形 + 至少有两个点坏掉的正方形 - 至少有三个点坏掉的正方形 + 至少有四个 ...

  5. C语言可变参数个数

    #include <stdio.h>#include <stdarg.h> void test(const char * format, ...); int main(void ...

  6. Ionic Js十七:侧栏菜单

    一个容器元素包含侧边菜单和主要内容.通过把主要内容区域从一边拖动到另一边,来让左侧或右侧的侧栏菜单进行切换. 效果图如下所示:   用法 要使用侧栏菜单,添加一个父元素,一个中间内容 ,和一个或更 ...

  7. CentOS源码安装搭建LNMP全过程(包括nginx,mysql,php,svn)

    服务器环境为:CentOS6.5 64位 目标:搭建LNMP(Linux + Nginx + MySQL + PHP +SVN),其中svn是用来代替ftp,方便开发中调试同步代码 相关目录:所有软件 ...

  8. TradingView 自定义指标

    TradingView 支持自定义指标,不过是把你要定义的指标写成一个 JS 源文件(customIndex.js),放在图表库 static 文件夹下.自定义指标 JS 源代码模板如下: __cus ...

  9. Servlet的一点小结

    1.什么是servlet servlet是一个Java applet,一个帮助程序.用于帮助浏览器从服务器中获取资源.浏览器-servlet-服务器三者的关系如图所示. 2.servlet的生命周期 ...

  10. mongodb的yum源配置和安装

    安装前注意: 此教程是通过yum安装的.仅限64位centos系统 安装步骤: 1.创建仓库文件: vi /etc/yum.repos.d/mongodb-org-3.4.repo 然后复制下面配置, ...