简单看看这两个类 String和StringBuilder

简介: 简单看看这两个类 String和StringBuilder

以前在园子里面讨论这两个类的文章有很多很多,并且还拿出了很多的测试报告,在什么情况下,谁比谁快,在什么情况下,该用谁不该用谁等等这些,我这里就不比较了,我就简单看看他们里面的内部实现,那就先看看String吧。

一:String类

  说到String类,资料上都说是存在于堆上的一个不可CURD的一个不可变的字符集,当然看到这句话之后就想要看看是不是这样的,然后就

好奇的写了以下代码。

1     class Program
2     {
3         static void Main(string[] args)
4         {
5             string s = "123";
6         }
7     }
method private hidebysig static void Main(string[l arqs) cil managed
.entrypoint
// Code size  8(5x8)  
.maxstack 1
.locals init ([6] string s) IL_5555: nop
IL 5551:(1dstr  "123" 
IL 5556: stloc.5 IL 9557: ret
} // end of method program::Main  

从上面的IL中也就仅仅发现一个ldstr指令,看得出clr把string做成了基元类型,也就没看到它具体转换成了什么样的方法,是不是调用了string

的构造函数,这个也不清楚,也就不知道具体怎么把这个有序字符集放到堆中,不过办法还是有的,我们随便挑一个方法看看,比如简单一点的

substring,我们看看它的源代码。

public string Substring int startIndex) substring 
return this.Substring(startIndex, this.Length - startIndex);
DynamicallyInvokable,|TargetedPatching0ptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCrit public string Substring(int startIndex, int length)
return this.InternalsubStringWithChecks(startIndex, length, false);
[SecurityCritical]
internal string InternalsubstringWithChecks(int startIndex, int length, bool fAlwaysCopy)
if (startIndex<0){
thrownewArgumentdtOfRangeException("startIndex",Environment.GetResourceString("ArgumentOutofRange StartIndex"));
if (startIndex > this.Length){
thrownew ArgumentOuOfRangeException("startIndex",Environment.GetResourceString("ArgumentOutOfRange StartIndexLargerTh
if (length <@)
throw new ArgumentOutfRangeException("length",Environment.GetResourceString("ArgumentOutOfRange NegativeLength"));
if (startIndex > this.Length - length)
throw new ArgumentOut0 RangeException("length",Environment.GetResourceString("ArgumentOutOfRange_IndexLength"));
}
if(length == 0)
return string.Empty;
return this.InternalsubString(startIndex, length, fAlwaysCopy); 

然后我们找到了一个核心的方法,这个internalSubstring里面定义了两个指针ptr和ptr2,ptr则指向新申请的内存块的首地址,ptr2则指向原始

字符串的首地址,最后将ptr2的位置偏移startindex个位置,最后我们就找到了终极方法string.wstrcpy。

// string
[SecurityCritical]
private unsafe string InternalsubString(int startIndex, int length, bool fAlwaysCopy
if(startIndex == 0 && length == this.Length &&!fAlwaysCopy)
{
return this;
string text = string.FastAllocateString(length);  根据length申请合适的内存 
fixed (char* ptr = &text.m firstChar) 空间大小  
{
fixed (char* ptr2 = &this.m firstChar)
将ptr指向新申请的text字符串的
{
string.wstrcpy(ptr,ptr2 +(IntPtr)startIndex, length); 首地址。  
}
return text;
-}  这个startindex是将内存  ptr2指向了当前字符串的内存首地址  
地址向后偏移了starindex个位置 

在string.wstrcpy方法里面,虽然看的迷迷糊糊,不过还是能看到类似这样的偏移操作,一点一点的将smem地址上的字符赋值给dmem中,

确实也就说明了在堆上是有序的字符集。

// string
SecurityCritical]
Finternal unsafe static void wstrcpy(char* dmem, char* smem, int charCount)
if(charCount >0){
if((dmem & 2)!= 0){
*dmem = *smem;
dmem +=(IntPtr)2/2; smem +=(IntPtr)2/2; charcount--;
while(charCount >= 8){
*(int*)dmem =*(int*)smem;
*(int*)(dmem +(IntPtr)4 / 2) = *(int*)(smem + (IntPtr)4 / 2);*(int*)(dmem +(IntPtr)8 / 2) = *(int*)(smem + (IntPtr)8 / 2);*(int*)(dmem +(IntPtr)12 / 2) = *(int*)(smem + (IntPtr)12 / 2): dmem +=(IntPtr)16 /2; smem +=(IntPtr)16/2: charCount -= 8;}
if((charCount &4)!= 0){
*(int*)dmem =*(int*)smem;
*(int*)(dmem +(IntPtr)4 / 2) = *(int*)(smem + (IntPtr)4 / 2); dmem +=(IntPtr)8/2; smem +=(IntPtr)8/2;
if((charCount & 2) != 0){
*(int*)dmem = *(int*)smem; dmem +=(IntPtr)4/2;
smem t=(IntPtr)4/2;

同样在上面的源代码中来说,substring操作并没有对原始字符串进行修改,而是把截取的值放到新申请的内存地址空间中,这也就说明了字符

串是不可修改的说法,当然如果设计者真的要做到原位修改,那肯定也是能做到的,为了佐证下,我再举一个经常用到的concat方法,不过在

FastAllocateString方法中,并没有看到他的源代码,所以只能说根据length申请合适的空间。

public static string Concat(string stro, string str1){
if(string.IsNullOrEmpty(str0)){
if(string.IsNullOrEmpty(str1)){
return string.Empty
}
return str1;}
else{
if(string.IsNullOrEmpty(str1))
return str0;
根据str0.length+str2.length申请合适空间
int length = str0.Length;
string text=string.FastAllocateString(length + str1.Length): string.FillstringChecked(text,0,str0);
string.FillstringChecked(text,length,str1);
} return text;  赋值第一部分

所以结论出来了: 当你对字符串进行大量操作的时候,会产生很多的新的字符串,这些字符串会大量零碎的占据着堆空间,大多都是生存期较短的,所以一般都是在堆的第一代上,所以会对gc产生了比较大回收压力。

二:StringBuilder

   看这个类的话,还是看一下它的源代码,就抽一个Append吧,从下面这个截图中看出来几个有意思的地方。

<1> 原来StringBuilder里面维护的是一个m_ChunkChars的字符数组。

<2> 如果当前的字符串的length<2,会直接给chunkchars数组复制,length>2的时候看到的是刚才string类中经典的wstrcpy用法,而

     这个时候ptr指向的是chunkChars[chunkLength]的首地址,而不像string中申请新的内存空间,所以从这里看,比string大大的节省

    了内存空间。

public unsafe stringBuilder Append(string value
if (value != null)
{
char[] chunkChars = this.m chunkchars;  原来里面维护了一个 
int length = value.Length; int chunkLength = this.m_chunklength; m_ChunkChars的字符数组 int num = chunkLength + length; if (num < chunkChars.Length)
if (length <= 2){
if (length >@){
chunkChars[chunkLength] =value[0];
if (length >1)
chunkChars[chunkLength + 1] = value[1];}}
else
(
fixed(char* smem = value)
fixed (char* ptr = &chunkChars[chunkLength])
string.wstrcpy(ptr,smem,length);
}
this.m ChunkLength =num;}
else
{ 
this.AppendHelper(value):

好了,具体他们的性能比较我也不说了,大家看着他们的原理凑合着用吧,简单的看看也只能看到这了,再看就漏点了。



目录
相关文章
|
16天前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
163 5
|
4月前
|
存储 安全 Java
String StringBuffer StringBuilder 区别详解与对比分析
本文详细解析了Java中String、StringBuffer和StringBuilder的区别,从可变性、线程安全性和性能三个方面进行对比,并结合具体应用场景分析了三者的适用范围。通过性能测试示例展示了它们在字符串拼接时的效率差异,同时提供了实际代码案例帮助理解。总结指出,String适合少量操作或线程安全场景,StringBuffer适用于多线程环境,而StringBuilder则在单线程下性能最优。开发者应根据需求选择合适的类以优化程序性能。文末还附有相关面试资料供参考。
766 2
|
4月前
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
90 0
|
7月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
160 11
|
7月前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
182 9
|
7月前
|
存储 JavaScript Java
课时44:String类对象两种实例化方式比较
本次课程的主要讨论了两种处理模式在Java程序中的应用,直接赋值和构造方法实例化。此外,还讨论了字符串池的概念,指出在Java程序的底层,DOM提供了专门的字符串池,用于存储和查找字符串。 1.直接赋值的对象化模式 2.字符串池的概念 3.构造方法实例化
106 1
|
11月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
337 2
|
11月前
|
安全
String、StringBuffer、StringBuilder的区别
String 由 char[] 数组构成,使用了 final 修饰,对 String 进行改变时每次都会新生成一个 String 对象,然后把指针指向新的引用对象。 StringBuffer可变并且线程安全;有一定缓冲区容量,字符串大小没超过容量,不会重新分配新的容量,适合多线程操作字符串; StringBuiler可变并且线程不安全。速度比StringBuffer更快,适合单线程操作字符串。 操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer