【Java笔记分享】StringBuffer字符串缓冲区

笔记源代码地址:B站 库米豪斯巴达锅铲祖师 空间

源代码在B站审核,要是审核大大觉得这个不能通过,我会在这里补发(因为只有源代码,真的很水)


字符串缓冲区的概念

缓冲区,可以将一些东西都暂时存在一起,慢慢地或一次性放出来 所以,一个字符串缓冲区对象就是一个将字符串都暂时存起来,然后一起输出的对象

或者我们可以这样理解: 中间的绿色区域的漏斗就是缓冲区

为什么要使用StringBuffer

实际应用时,我们推荐选择与它相似的类StringBuilder,它是非线程安全的,而我们手动确保线程安全的效率更高

众所周知,Java中的String类是一个引用数据类型,即可以创建出对象,用引用来指代这个对象,进行其他操作。所以理论上,每个字符串都是一个独立的对象,会输出不同的哈希值

而其他大部分语言,比如python,会有这种现象 ->

"abc" == "abc"		返回结果:True

所以在JavaSE中,以任何形式定义的字符串:"string!" 会被Java“记住”,也就是下次再看到这个字符串,即字符串 "string!" ,会认为这两个是同一个字符串 (JavaWeb里实测要用equals()方法)

实现的途径就是: 在方法区内存开辟一个“字符串常量池”,把字符串就像int类型一样,存到这个常量池中,因为字符串本质上就是一堆字符,也就是一堆byte。下次再发现字符串,去常量池里面对应,如果找到byte序列一样的,将当前字符串的内存地址设置成常量池里面和它长得一样的字符串的地址

然后我的笔记中有这么一个刁钻的例子:

//以下代码运行起来十分刺激
String s2 = "";
	//这个for循环直接创建51个字符串(i)和51个字符串(运行区里的)
for(int i = 0 ; i <= 50 ; i++) {
	s2 += i;
	System.out.println(s2);
}

运行后会产生102个不同的字符串,再这样连起来输出会很刺激,当你的字符串多到一定程度时,JVM就会想关机了

这时,我们发现,String是由一个一个的byte组成的。我们只需要把这些byte连起来,就是一个未成形的新字符串,还没有进入方法区内存(就像是氨基酸已经接成了多肽,但还没有被高尔基体掰过所以不是一个蛋白质,就不会出这个细胞,且不会产生作用)

这些byte的存放地点就是StringBuffer对象

StringBuffer的基本原理(附有源码)

由上面的介绍,我们可以推断出来StringBuffer的底层是一个byte数组:

/**
  * The value is used for character storage.
  */
byte[] value;

注:此处源代码不能直接在StringBuffer里面找到,而是在它的父类AbstractStringBuilder(抽象的字符串缓冲区)

使用时,程序员将一个字符串对象用 append() 方法存入,这样至少可以避免中间产物占用内存,如上面那个刁钻的例子中,若我们用了StringBuffer,实际存储在方法区内存的只会有53个字符串,而不是102个:"" "0" "1" "2" ... "49" "50" "0123456 ... 484950"

append()方法将里面的 super.append(str) 打开后的源码:

@Override
@IntrinsicCandidate
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    if (str == null) {
        return appendNull();
    }
    int len = str.length();
    ensureCapacityInternal(count + len);
    putStringAt(count, str);
    count += len;
    return this;
}

以上的 count 变量相当于ArrayList中的size()方法返回值(没学过的同学也别慌,就是字符串对应的byte数组长度) 总体执行步骤是: 确保上次 toString() 方法传过来的缓冲字符串是null,且要加入的字符串不是null时,会去比较加入后字符串的长度是否超过缓冲区的大小。超过则扩容:ensureCapacityInternal(count + len);,否则直接存到末尾:putStringAt(count, str); 然后它会重新计算长度,并将当前的对象作为返回值,可以选择用一个引用把它接住,也可以不接住,但是接住了可以留一个备份,因为它的toString()方法会将本次的返回结果存入变量 toStringCache

@Override
@IntrinsicCandidate
public synchronized String toString() {
    if (toStringCache == null) {
        return toStringCache =
                isLatin1() ? StringLatin1.newString(value, 0, count)
                           : StringUTF16.newString(value, 0, count);
    }
    return new String(toStringCache);
}

由此,我们又发现(啊!事真多!),StringBuffer是有大小的。 StringBuffer的默认大小是16(可以存16个byte),但我们可以手动调节。调节时候自己估算一下,太短了难以避免扩容,太长了占空间而且占着茅坑不拉屎。一个英文字母是1 byte,一个汉字一般是2 byte,这样简单计算一下,用手和脑子估算的同学建议向上取整(因为扩容的代价真的比稍微浪费一点空间要大很多)

我们最常需要用的的两个StringBuffer的构造器:

/**
  * Constructs a string buffer with no characters in it and an
  * initial capacity of 16 characters.
  */
@IntrinsicCandidate
public StringBuffer() {
    super(16);
}

/**
  * Constructs a string buffer with no characters in it and
  * the specified initial capacity.
  *
  * @param      capacity  the initial capacity.
  * @throws     NegativeArraySizeException  if the {@code capacity}
  *             argument is less than {@code 0}.
  */
@IntrinsicCandidate
public StringBuffer(int capacity) {
    super(capacity);
}

finally——

笔记上只有粗略解释,这里的详解是我呕心沥血分析源码写出来的。但是还请大佬们纠错,不要纵容我误人子弟谢谢 ( * · ω · * )

java
89 views
Comments
登录后评论
Sign In
·

有意思!!! +1

第一次给大家做真正意义上的笔记分享,因为之前的一篇笔记分享只是将源代码贴出来,而且我感觉其实方法介绍没啥好讲的背就完了,于是收到了 @0x574859 的建议,据此我做了较高质量的笔记分享

大家是喜欢严肃一点的纯讲解还是像这样穿插例子加深印象的