1.源码上看 .NET 中 StringBuilder 拼接字符串的码分实现
2.StringBuilder为ä»ä¹çº¿ç¨ä¸å®å
¨ï¼
3.stringbufferä¸stringbuilderçåºå«?
4.为什么?为什么StringBuilder是线程不安全的?
5.stringbuffer扩容为什么是2倍+2?
源码上看 .NET 中 StringBuilder 拼接字符串的实现
StringBuilder在.NET Core中的实现核心在于动态管理字符数组,以此来高效地拼接字符串。码分实际上,码分StringBuilder内部使用字符数组来存储字符串信息,码分这与它的码分动态增长特性相匹配。然而,码分rsi自带指标源码直接使用固定大小的码分数组存在局限性,因此,码分.NET Core采用了单链表结构来优化拼接效率,码分避免了复制操作带来的码分性能损耗。每个StringBuilder对象都包含一个指向其前一个对象的码分引用,这构成了链表的码分结构。通过这种方式,码分当需要拼接的码分字符串长度超过当前字符数组容量时,可以创建新节点,码分而不必复制数据。宝塔主升浪源码这种方法在频繁进行尾部拼接的场景中表现出较高的效率。此外,StringBuilder还利用了链表的特性,简化了对尾部数据的添加操作,从而提升了操作性能。尽管链表在随机访问方面有其局限性,但在最常见的使用模式下,这种方法仍然有效。总的来说,StringBuilder通过结合字符数组与单链表,实现了高效、灵活的字符串拼接机制。
StringBuilder为ä»ä¹çº¿ç¨ä¸å®å ¨ï¼
1ã为ä»ä¹è¾åºå¼è·é¢æå¼ä¸ä¸æ ·æ们å çä¸ä¸StringBuilderç两个æååéï¼è¿ä¸¤ä¸ªæååéå®é ä¸æ¯å®ä¹å¨AbstractStringBuilderéé¢çï¼StringBuilderåStringBufferé½ç»§æ¿äºAbstractStringBuilderï¼
//åå¨å符串çå ·ä½å 容char[] value;//å·²ç»ä½¿ç¨çå符æ°ç»çæ°éint count;
åçStringBuilderçappend()æ¹æ³ï¼
@Overridepublic StringBuilder append(String str) { super.append(str); return this;}
StringBuilderçappend()æ¹æ³è°ç¨çç¶ç±»AbstractStringBuilderçappend()æ¹æ³
public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this;}
æ们å ä¸ç®¡ä»£ç ç第äºè¡å第å è¡å¹²äºä»ä¹ï¼ç´æ¥ç第ä¸è¡ï¼count += lenä¸æ¯ä¸ä¸ªååæä½ãå设è¿ä¸ªæ¶åcountå¼ä¸ºï¼lenå¼ä¸º1ï¼ä¸¤ä¸ªçº¿ç¨åæ¶æ§è¡å°äºç¬¬ä¸è¡ï¼æ¿å°çcountå¼é½æ¯ï¼æ§è¡å®å æ³è¿ç®åå°ç»æèµå¼ç»countï¼æ以两个线ç¨æ§è¡å®åcountå¼ä¸ºï¼èä¸æ¯ãè¿å°±æ¯ä¸ºä»ä¹æµè¯ä»£ç è¾åºçå¼è¦æ¯å°çåå ã
2ã为ä»ä¹ä¼æåºArrayIndexOutOfBoundsExceptionå¼å¸¸ã
æ们çåAbstractStringBuilderçappend()æ¹æ³æºç ç第äºè¡ï¼ensureCapacityInternal()æ¹æ³æ¯æ£æ¥StringBuilder对象çåcharæ°ç»ç容éè½ä¸è½çä¸æ°çå符串ï¼å¦æçä¸ä¸å°±è°ç¨expandCapacity()æ¹æ³å¯¹charæ°ç»è¿è¡æ©å®¹ã
private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity);}
æ©å®¹çé»è¾å°±æ¯newä¸ä¸ªæ°çcharæ°ç»ï¼æ°çcharæ°ç»ç容éæ¯åæ¥charæ°ç»ç两ååå 2ï¼åéè¿System.arryCopy()å½æ°å°åæ°ç»çå 容å¤å¶å°æ°æ°ç»ï¼æåå°æéæåæ°çcharæ°ç»ã
void expandCapacity(int minimumCapacity) { //计ç®æ°ç容é int newCapacity = value.length * 2 + 2; //ä¸é´çç¥äºä¸äºæ£æ¥é»è¾ ... value = Arrays.copyOf(value, newCapacity);}
Arrys.copyOf()æ¹æ³
public static char[] copyOf(char[] original, int newLength) { char[] copy = new char[newLength]; //æ·è´æ°ç» System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy;}
AbstractStringBuilderçappend()æ¹æ³æºç ç第å è¡ï¼æ¯å°String对象éé¢charæ°ç»éé¢çå 容æ·è´å°StringBuilder对象çcharæ°ç»éé¢ï¼ä»£ç å¦ä¸ï¼
str.getChars(0, len, value, count);
getChars()æ¹æ³
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { //ä¸é´çç¥äºä¸äºæ£æ¥ ... System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }
æ·è´æµç¨è§ä¸å¾
å设ç°å¨æ两个线ç¨åæ¶æ§è¡äºStringBuilderçappend()æ¹æ³ï¼ä¸¤ä¸ªçº¿ç¨é½æ§è¡å®äºç¬¬äºè¡çensureCapacityInternal()æ¹æ³ï¼æ¤å»count=5ã
è¿ä¸ªæ¶å线ç¨1çcpuæ¶é´çç¨å®äºï¼çº¿ç¨2继ç»æ§è¡ã线ç¨2æ§è¡å®æ´ä¸ªappend()æ¹æ³åcountåæ6äº
线ç¨1继ç»æ§è¡ç¬¬å è¡çstr.getChars()æ¹æ³çæ¶åæ¿å°çcountå¼å°±æ¯6äºï¼æ§è¡charæ°ç»æ·è´çæ¶åå°±ä¼æåºArrayIndexOutOfBoundsExceptionå¼å¸¸ã
è³æ¤ï¼StringBuilder为ä»ä¹ä¸å®å ¨å·²ç»åæå®äºãå¦ææ们å°æµè¯ä»£ç çStringBuilder对象æ¢æStringBuffer对象ä¼è¾åºä»ä¹å¢ï¼
å½ç¶æ¯è¾åºå¦ï¼
é£ä¹StringBufferç¨ä»ä¹æ段ä¿è¯çº¿ç¨å®å ¨çï¼è¿ä¸ªé®é¢ä½ ç¹è¿StringBufferçappend()æ¹æ³éé¢å°±ç¥éäºã
stringbufferä¸stringbuilderçåºå«?
äºè çåºå«ä¸»è¦æ¯å¨è¿è¡é度å线ç¨å®å ¨è¿ä¸¤æ¹é¢ã1ãStringBuffer ä¸ StringBuilder ä¸çæ¹æ³ååè½å®å ¨æ¯çä»·ç
2ãåªæ¯StringBuffer ä¸çæ¹æ³å¤§é½éç¨äº synchronized å ³é®åè¿è¡ä¿®é¥°ï¼å æ¤æ¯çº¿ç¨å®å ¨çï¼è StringBuilder 没æè¿ä¸ªä¿®é¥°ï¼å¯ä»¥è¢«è®¤ä¸ºæ¯çº¿ç¨ä¸å®å ¨çã
3ãå¨å线ç¨ç¨åºä¸ï¼StringBuilderæçæ´å¿«ï¼å 为å®ä¸éè¦å éï¼ä¸å ·å¤å¤çº¿ç¨å®å ¨èStringBufferåæ¯æ¬¡é½éè¦å¤æéï¼æçç¸å¯¹æ´ä½ã
为什么?为什么StringBuilder是线程不安全的?
在比较 String、StringBuilder 和 StringBuffer 时,提及了 StringBuilder 是macd j组合指标源码非线程安全的,那么其原因何在?
在查看 StringBuilder 或 StringBuffer 的源码后,可以发现 StringBuilder 在 append 操作中并未使用线程同步,而 StringBuffer 在大部分方法中使用了 synchronized 关键字进行方法级别的同步。这种说法是正确的,通过对比源码亦能验证。
然而,这并未解释为何 StringBuilder 会是线程不安全的,为何需要使用 synchronized 来确保线程安全?以及如果未使用同步会发生什么异常?
接下来,我们将分别解答以上问题。
通过示例代码,构建一个 StringBuilder,并创建 个线程,每个线程拼接字符串 "a" 次。理论上,完成线程执行后,源码资本企业文化打印的结果应为 ,但多次执行后,打印结果通常少于 ,且存在一定概率出现异常信息。
StringBuilder 线程不安全的原因在于其内部处理字符串的两个关键成员变量:char 数组 value 和 count。StringBuilder 通过不断扩容和增加 count 来实现字符串的 append 操作。在 append 方法中,count 的 "+= 操作是线程不安全的,可能导致两个线程同时读取到 count 值为 5,执行加 1 操作后,都变为 6,而非预期的 7,导致不期望的结果。
在异常堆栈信息中,可以发现发生异常的布林线玩法源码代码在 AbstractStringBuilder 的 append 方法中。核心操作为将传入的 String 对象复制到 value 中。异常发生的原因是程序试图访问下标为 7 的位置,而此时 value 的实际下标只到 6。这与 count 被错误地少加有关。在执行 str.getChars 方法之前,需根据 count 校验 value 是否已用尽,若已用尽,则进行扩容。append 方法中的对应代码为:
确保容量内部的具体实现:
当 count 应为 7 而 value 长度为 6 时,本应触发扩容。但由于并发导致 count 为 6,假设 len 为 1,则传递的 minimumCapacity 为 7,并不会进行扩容操作。这导致后续执行 str.getChars 方法进行复制操作时访问不存在的位置,从而抛出异常。
在此基础上,我们简单查看了扩容方法中的 newCapacity 方法,核心是将新数组长度扩充为原来的两倍再加 2,作为 Arrays.copyOf 的参数进行扩容。
通过上述分析,我们真正理解了 StringBuilder 线程不安全的原因。在学习和实践中,不仅应掌握结论,还需理解其底层原理,并学会分析底层原理的方法。
stringbuffer扩容为什么是2倍+2?
在常规用法中,StringBuffer和StringBuilder在功能上差别不大,主要区别在于StringBuffer具备线程安全性,但效率相对较低,而StringBuilder则线程不安全但效率更高。不过在扩容机制上,两者一致。下面以StringBuffer为例进行深入分析。
首先,追踪StringBuffer源码,发现它继承自AbstractStringBuilder。这意味着StringBuffer和StringBuilder是“亲兄弟”,拥有共同的抽象父类AbstractStringBuilder。在这个抽象类中,定义了字符串存储的定长字节数组,并在追加字符串时,当长度超过数组长度时,通过数组复制方式实现扩容。
容量设置上,StringBuffer提供了默认容量和自定义容量的构造方法,即使默认构造方法也会设置初始容量为。在实际开发中,容量不足时,通过append()方法追加字符串触发动态扩容。
append()方法实际上调用的是AbstractStringBuilder的append()方法,进入内部后,发现关键在于ensureCapacityInternal()方法。该方法确保内部容量足够,通过count+len参数计算追加后字符串总长度,实现动态扩容。
在ensureCapacityInternal()方法中,首先利用二进制位右移运算计算数组原有容量,考虑到编码方式(默认Latin1或UTF-),判断新字符串长度是否超过原有容量。若超过,则利用Arrays.copyOf()方法复制并创建新数组,将当前数组值赋给新数组,完成扩容。
newCapacity()方法计算扩容后数组长度,通常在新字符串长度基础上增加一定比例,确保足够容纳新追加的字符串。而新长度计算逻辑通常涉及Math.max()方法,确保不会超出Integer最大值,避免内存溢出异常。
StringBuffer扩容机制核心如下:若一次追加字符串长度超过当前容量,扩容规则为当前容量*2+2;如果追加长度超出初始容量且按当前容量*2+2扩容后仍不足,直接扩容至与新字符串长度相等;后续追加继续遵循当前容量*2+2规则。扩容为2倍+2的原因是为了减少内存分配次数和内存碎片,提高性能和效率。
为了验证上述规则,可设计实验案例,观察StringBuffer与StringBuilder的扩容表现。至此,详细解释了StringBuffer扩容机制及其规则,希望能对理解Java中字符串操作有所帮助。