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