StringBuffer 和 StringBuilder

StringBuilder 和 StringBuffer 均继承自 AbstractStringBuilder,而 StringBuilder 在 StringBuffer 之后出现。按照顺序逐个进行分析 AbstractStringBuilder,StringBuffer,StringBuilder 。

AbstractStringBuilder

AbstractStringBuilder 构造器

AbstractStringBuilder 的无参构造器的作用是为了让子类能够序列化和反序列化。另一个有参构造函数传入的 capacity,实例化存储字符序列的字符数组 value。

AbstractStringBuilder() {    }    AbstractStringBuilder(intcapacity) {        value =newchar[capacity];    }

append() 方法

append(String str) 的操作如下:

判断 str 是否为空,若为空,则直接调用 appendNull() 并返回;

计算(count + len)追加 str 之后的长度,并确保存储字符序列的字符数组足够长;

str.getChars() 方法将 str 复制到字符数组 value(存储了 StringBuffer 字符序列);

返回当前对象。

ensureCapacityInternal() 方法会检查字符数组 value 的容量是否足以存储追加之后的字符序列,不足则会进行扩容。count 表示 value 中下一个可用位置的下标。

publicAbstractStringBuilderappend(String str){if(str ==null)returnappendNull();intlen = str.length();        ensureCapacityInternal(count + len);        str.getChars(0, len, value, count);        count += len;returnthis;    }

appendNull()

appendNull() 是一个私有方法,它同样先确保 value 容量足够,然后追加 'n','u','l','l' 四个字符到 value 数组中。

AbstractStringBuilder.java 代码片段

privateAbstractStringBuilderappendNull(){intc = count;        ensureCapacityInternal(c +4);finalchar[] value =this.value;        value[c++] ='n';        value[c++] ='u';        value[c++] ='l';        value[c++] ='l';        count = c;returnthis;    }

其它 append 的重载方法追加内容流程类似:

判断参数是否为空;

确保 value 容量足够;

执行追加操作;

返回当前对象。

ensureCapacity(int minimumCapacity) 和 ensureCapacityInternal(int minimumCapacity)

ensureCapacity(int minimumCapacity) 由 public 修饰,而 ensureCapacityInternal(int minimumCapacity) 由 private 修饰,前者调用了后者。前者参数只有输入的 minimumCapacity 为正数时才有效。

publicvoidensureCapacity(intminimumCapacity){if(minimumCapacity >0)            ensureCapacityInternal(minimumCapacity);    }

ensureCapacityInternal(int minimumCapacity) 先判断 `minnumCapacity 是否大于字符数组 value 的长度,若超过,则调用 newCapacity(int minCapacity) 计算新的长度,再 Arrays.copyOf() 方法对数组进行了复制。Arrays.copyOf(char[] original, int newLength) 会将 original 复制到一个长度为 newLength 的新数组中并返回。

newCapacity(int minCapacity) 与 hugeCapacity(int minCapacity)

newCapacity(int minCapacity) 返回了一个大于等于 minCapacity 的整数。扩容时先将原来的容量乘以 2 再加 2,如果容量未达到要求,则将 newCopacity 的值设置为传入的 minCapacity。

如果计算的 newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0(MAX_ARRAY_SIZE 是一个常量,值为 Integer.MAX_VALUE - 8),则通过 hugeCapacity(int minCapacity) 来决定扩容后大小。

privateintnewCapacity(intminCapacity){// overflow-conscious codeintnewCapacity = (value.length <<1) +2;// 将 value.length 乘以 2 再加 2if(newCapacity - minCapacity <0) {            newCapacity = minCapacity;        }return(newCapacity <=0|| MAX_ARRAY_SIZE - newCapacity <0)            ? hugeCapacity(minCapacity)            : newCapacity;    }

hugeCapacity(int minCapacity) 先判断是否产生了整数溢出(传入的 minCapacity 为负数时溢出),溢出了则抛出异常,否则将容量设置为一个最大不超过 MAX_ARRAY_SIZE 的值。

privateinthugeCapacity(intminCapacity){if(Integer.MAX_VALUE - minCapacity <0) {// overflowthrownewOutOfMemoryError();        }return(minCapacity > MAX_ARRAY_SIZE)            ? minCapacity : MAX_ARRAY_SIZE;    }

总而言之,若 value 的容量较小,则每次容量至少扩容为原来的 2 倍再加 2;若大小还不满足要求,则直接扩容为 minCapacity,但是最大容量不会超过 MAX_ARRAY_SIZE,也就是说 StringBuffer 表示的字符序列的最大长度为 Integer.MAX_VALUE - 8,继续 append 则会抛出 OutofMemoryError() 异常。

trimToSize() 方法

trimtoSize() 方法将 value 的容量压缩到和字符序列的长度一致,在确定不增加字符序列长度时可以调用此方法释放一部分内存。可以释放 (sbf.capacity() - sbf.length())*2 字节的内存。

publicvoidtrimToSize(){if(count < value.length) {            value = Arrays.copyOf(value, count);        }    }

setLength(int newLength) 方法

设置序列的新长度,若 newLength > sbf.length(),则超出部分填充 '\0';若 newLength < sbf.length(),则直接将 count 移动到 newLength 位置,下次直接从 newLength 位置开始追加内容。

publicvoidsetLength(intnewLength){if(newLength <0)thrownewStringIndexOutOfBoundsException(newLength);        ensureCapacityInternal(newLength);if(count < newLength) {            Arrays.fill(value, count, newLength,'\0');        }        count = newLength;    }

charAt(int index)

这个方法很简单,取出下标为 index 的字符,若 index 不在字符索引范围之内则抛出 StringIndexOutofBoundsException。

publiccharcharAt(intindex){if((index <0) || (index >= count))thrownewStringIndexOutOfBoundsException(index);returnvalue[index];    }

codePointAt(int index)

此方法返回索引为 index 的字符的 16 位 Unicode 编码系统的码点。编码系统将字符映射到一个数字,这个数字就是码点,例如 'a' 映射的码点就是 97。需要注意的是,char类型是 2 个字节,但码点的的范围却可以超过 65535 ,因为有些字符需要用 4 个字节表示。(参考:Java中码点和代码单元

如:

StringBuilder sb =newStringBuilder("𝕆");// 这是现实世界中的一个字符,但 Java 中不能用 char 接收,而要用 String。System.out.println("len: "+ sb.length() +", codePoint:"+ sb.codePointAt(0));// 输出:len: 2, codePoint:120134

index012345MAX_ARRAY_SIZE......a𝕆bcode point012countcapacity

codePointBefor(int index)

返回前一个码点值,例如:

代码返回

"a𝕆b".codePointBefore(1)97, 即 'a' 的码点

"a𝕆b".codePointBefore(2)55349,即 "𝕆" 前半个部分两个字节对应的 char 字符的码点

"a𝕆b".codePointBefore(3)120134,即字符 "𝕆" 的码点

若 index 恰好落在了某个2字节码点的后半部分,则返回的是前半部分对应的 char 字符的码点。

codePointCount(int beginIndex, int endIndex)

此方法返回 [beginIndex, endIndex) 字符序列之间码点的数量,可以通过 sb.codePointCount(0, sb.length()) 来统计当前字符序列中总共有多少个 Unicode 字符。

StringBuilder sb =newStringBuilder("a𝕆b");System.out.println(sb.codePointCount(0,1));// 1System.out.println(sb.codePointCount(0,2));// 2System.out.println(sb.codePointCount(0,3));// 2System.out.println(sb.codePointCount(0,4));// 3

offsetByCodePoints(int index, int codePointOffset)

此方法表示从 index 开始,向右偏移 codePointOffset 个码点之后的索引。

例如:

StringBuilder sb =newStringBuilder("a𝕆b");System.out.println(sb.offsetByCodePoints(0,0));// 0System.out.println(sb.offsetByCodePoints(0,1));// 1System.out.println(sb.offsetByCodePoints(0,2));// 3System.out.println(sb.offsetByCodePoints(0,3));// 4System.out.println(sb.offsetByCodePoints(1,0));// 1System.out.println(sb.offsetByCodePoints(1,1));// 3System.out.println(sb.offsetByCodePoints(1,2));// 4 index 落在𝕆的前半部分System.out.println(sb.offsetByCodePoints(2,1));// 3System.out.println(sb.offsetByCodePoints(2,2));// 4 index 落在𝕆的后半部分

getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

此方法将 [srcBegin, srcEnd) 范围的字符序列复制到字符数组 dst 中,存放下标从 dstBegin 开始。同样下标不能越界。

publicvoidgetChars(intsrcBegin,intsrcEnd,char[] dst,intdstBegin){if(srcBegin <0)thrownewStringIndexOutOfBoundsException(srcBegin);if((srcEnd <0) || (srcEnd > count))thrownewStringIndexOutOfBoundsException(srcEnd);if(srcBegin > srcEnd)thrownewStringIndexOutOfBoundsException("srcBegin > srcEnd");    System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}

setCharAt(int index, char ch)

将索引为 index 的字符设置为 ch,因为底层本身存储的就是字符数组,所以这个方法很高效。

replace(int start, int end, String str)

此方法将 [start, end) 字符序列替换为 str,替换时先确保容量足够,然后将 [end, count) 之间的序列复制到后面,然后再将 str 复制到中间。同样有索引越界检查。

publicAbstractStringBuilderreplace(intstart,intend, String str){if(start <0)thrownewStringIndexOutOfBoundsException(start);if(start > count)thrownewStringIndexOutOfBoundsException("start > length()");if(start > end)thrownewStringIndexOutOfBoundsException("start > end");if(end > count)        end = count;intlen = str.length();intnewCount = count + len - (end - start);    ensureCapacityInternal(newCount);    System.arraycopy(value, end, value, start + len, count - end);    str.getChars(value, start);    count = newCount;returnthis;}

substring(int start), subSequence(int start, int end), substring(int start, int end)

前 2 个方法调用了第 3 个方法。此方法会创建一个新的 String 对象并返回。

publicStringsubstring(intstart,intend){if(start <0)thrownewStringIndexOutOfBoundsException(start);if(end > count)thrownewStringIndexOutOfBoundsException(end);if(start > end)thrownewStringIndexOutOfBoundsException(end - start);returnnewString(value, start, end - start);}

insert(int offset, String str)

此方法将 str 插入到字符序列中,插入索引从 offset 开始。

insert() 方法有若干重载方法,这些重载方法套路大同小异,流程如下:

检查索引是否越界

判断是否为空,若为空则转化为 "null" 再继续

确保 value 容量足够插入

将 [offset, count] 部分的代码复制到后面

将 str 复制到中间

indexOf(String str) 与 lastIndexOf(String str)

这两个 API 直接调用了 String 的静态方法,分别返回字符序列中第一次出现 str 的索引和最后一次出现 str 的索引。

reverse()

此方法将字符序列反转并返回,注意,代码并非单纯地将 value 数组反转,而是按照码点将字符反转。否则,反转之后可能出现乱码。

例如:

StringBuilder sb =newStringBuilder("a𝕆b");System.out.println(sb.reverse());// "b𝕆a"

publicAbstractStringBuilderreverse(){booleanhasSurrogates =false;intn = count -1;for(intj = (n-1) >>1; j >=0; j--)intk = n - j;charcj = value[j];charck = value[k];        value[j] = ck;        value[k] = cj;if(Character.isSurrogate(cj) ||            Character.isSurrogate(ck)) {            hasSurrogates =true;        }    }if(hasSurrogates) {        reverseAllValidSurrogatePairs();    }returnthis;}

以上就是 AbstractStringBuilder 的内容了。

StringBuffer

StringBuffer 是一个可变的、线程安全的字符序列,在 JDK1.0 的时候出现。它继承自 AbstractStringBuilder,复用了父类的数据存储结构和几乎所有的方法。通过在方法前面增加 synchronized 关键字来达到线程安全的效果。

StringBuffer 构造器

StringBuffer 包含了 4 个构造方法,所有的构造方法都调用了父类的构造器 AbstractStringBuilder(int capacity),前面提到这个构造器实例化了字符数组 value。由下面代码此可知,StringBuffer 默认最小的 capacity (value 的长度)为 16,除非手动传入一个值。

publicStringBuffer(){// 实例化 value = new char[16]super(16);    }publicStringBuffer(intcapacity){// 自定义 capacitysuper(capacity);    }publicStringBuffer(String str){// 实例化 value = new char[str.length()+16],并将初始序列设置为 strsuper(str.length() +16);        append(str);    }publicStringBuffer(CharSequence seq){this(seq.length() +16);        append(seq);    }

StringBuffer append(String str) , StringBuffer insert(int offset, boolean b) 和 StringBuffer replace(int start, int end, String str)

append(String str)被synchronized` 关键字修饰,这是线程安全的保证。它先将成员变量 toStringCache 置为了空,然后调用父类的 append(String str) 方法,最后返回当前对象。toStringCache 是 StringBuffer toString() 方法返回值的缓存,在调用 toString() 方法且 toStringCache 为空时被赋值,在 StringBuffer 对象发生改变的时候被清空(或者说缓存失效),即赋值为 null。

@OverridepublicsynchronizedStringBufferappend(String str){        toStringCache =null;super.append(str);returnthis;    }

StringBuffer 复用了父类 AbstractStringBuilder 的几乎所有 API, 然后用 synchronized 修饰自己的方法。如果是读取操作,则直接调用父类方法,如:String substring(int start) 方法。如果是增、删、改操作则先将 toStringCache 清空,再调用父类方法。

下面的这个 insert 方法没有被 synchronized 修饰,原因它调用了同一个 StringBuffer 对象的其它方法。从注释也可以看出原因。

当前方法调用了父类方法 AbstractStringBuilder insert(int offset, boolean b),父类方法又调用了 AbstractStringBuilder insert(int offset, String str) 和 AbstractStringBuilder insert(int dstOffset, CharSequence s, int start, int end),而着两个方法都均已被子类 StringBuffer 覆盖,所以不需要再使用一次 synchronized 获取对象锁,以提高运行效率。

@OverridepublicStringBufferinsert(intdstOffset, CharSequence s){// Note, synchronization achieved via invocations of other StringBuffer methods// after narrowing of s to specific type// Ditto for toStringCache clearingsuper.insert(dstOffset, s);returnthis;}

String toString()

toString() 将字符序列转化为字符串。它先判断了 toStringCache 是否为空;若为空,则复制出一个数组并赋值给 toStringCache 并返回;若不为空则直接返回。toStringCache 保证了连续多次 toString() 方法不会重复产生同样的字符串对象,在一定程度上节省了空间,提高了效率。但真正能提高多少效率还需要打个问号,毕竟缓存的结果既不需要通过 CPU 的密集计算得到也不需要 IO 操作得到。

@OverridepublicsynchronizedStringtoString(){if(toStringCache ==null) {        toStringCache = Arrays.copyOfRange(value,0, count);    }returnnewString(toStringCache,true);}

以上就是 StringBuffer 在 AbstractStringBuilder 基础上增加的内容。在方法前面增加 synchronized 关键字,通过对象锁来保证线程安全,但这极大损失了效率。

StringBuilder

StringBuilder 在 JDK1.5 时引入,它与 StringBuffer 一样继承了 AbstractStringBuilder,也复用了父类的存储结构和几乎所有 API。它的引入是为了在单线程环境下替代 StringBuffer,毕竟在每线程安全问题的场景下它的效率要高很多。

StringBuilder 构造器

StringBuilder 的构造器和 StringBuffer 一模一样。

publicStringBuilder(){super(16);}publicStringBuilder(intcapacity){super(capacity);}publicStringBuilder(String str){super(str.length() +16);    append(str);}publicStringBuilder(CharSequence seq){this(seq.length() +16);    append(seq);}

append(), insert(), delete()

StringBuilder 完全复用了父类 AbstractStringBuilder 的方法,没有新增任何特殊代码。

@OverridepublicStringBuilderappend(Stringstr){super.append(str);returnthis;}

USB Microphone https://www.soft-voice.com/

Wooden Speakers  https://www.zeshuiplatform.com/

亚马逊测评 www.yisuping.cn

深圳网站建设www.sz886.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,706评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 6,874评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,519评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,582评论 2 9