你真的会用StringBuffer吗?

最近在看《How Tomcat Works》这本书,其中有这样一句代码:

public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];

从我会用StringBuffer开始,一直都是

StringBuffer sb = new StringBuffer();
sb.append("sb");
sb.append("another sb");
sb.append("只要用StringBuffer了就能提高性能了,管它呢");

关键点就在StringBuffer的构造函数里。

如果不传任何参数,capacity默认的值是16.

如果传一个int型的值,就把这个值赋给capacity.

如果传的是一个字符串,capacity的值就是 字符串的长度+16.

StringBuffer把capacity传给了它的父类AbstractStringBuilder,它的父类用capacity做了什么事?

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

/**
 * The count is the number of characters used.
 */
int count;
AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

就是new了一个char型的数组。

******************************************************************************************************************************************************************

重点跟一下StringBuffer的append方法。 假设代码是 new StringBuffer("abc");  看上图140行,在构造函数内部调用了append方法。

StringBuffer调用父类的append方法:

@Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
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还没有被赋值过,此时count=0。  如果StringBuffer sb = new StringBuffer(); sb.append("sb"); 这种情况下count的值也是0. 只要是第一次调用append,count的值都是0。   假设我们传入的初始字符串是"abc"。 那么此处的count+len就是3。

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

3-19<0 所以不会调用expandCapacity(minimumCapacity);

但是当我们再次append一个长度为17的字符串时。 count+len=3+17=20。 这时就会调用expandCapacity(minimumCapacity);

    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }

可以看到,这个方法的目的是扩大载荷。

通俗点说,我有3个苹果,想买个篮子来装。 JDK根据我的实际情况,帮我做了一个能装19个苹果的篮子。 如果一开始我一个苹果都没有,JDK就会给我做一个能装16个苹果的篮子。

篮子里已经装了3个苹果了。现在我又有17个苹果了,这个篮子已经装不下了。

JDK试着将篮子的容量扩大为现有容量的2倍+2,如果能装完,就好得很。 如果还是装不完,就把容量改为 刚好能 容的下 已有 和 现有的苹果数量之和。

注意此处为什么老是判断小于0.   因为newCapacity = value.length * 2 + 2;    minimumCapacity = count + len; 两个都是通过计算得到的。 int型的数如果太大就会溢出,溢出后的结果就是负数了。溢出时最高位是1,也就是符号位是1,可不就是负数嘛。

可以看到扩充载荷的时候有一个数组的拷贝动作。expandCapacity里有好几行代码呢。

总结:

如果在new StringBuffer的时候不传递一个字符串或者int型的值, 那么capacity的值将会是默认的16。以后append字符串的时候调用expandCapacity的几率比较大,这个方法里有好几行代码呢,而且还有数组的拷贝动作。 如果每次append的字符串长度都差不多,那么可以不传任何值。 但是如果append的字符串一次比一次长,突然某一次字符串的长度非常大时。这时capacity是这一次字符串的长度, 再append一次的话,capacity将会增加2倍。这个时候就很有可能出现浪费。

capacity调小了会频繁调用expandCapacity,调大了可能会出现浪费。

建议:

在使用StringBuffer的时候应该对最终字符串的长度有一个预判,然后传入capacity的值。这样就能避免浪费和频繁扩展capacity。 实在不行,capacity就以 某一次append的字符串长度最大的那个来算。

时间: 2024-10-18 01:30:57

你真的会用StringBuffer吗?的相关文章

你真的会用Gson吗?Gson使用指南(2)

注:此系列基于Gson 2.4. 上一篇文章 你真的会用Gson吗?Gson使用指南(1) 我们了解了Gson的基础用法,这次我们继续深入了解Gson的使用方法. 本次的主要内容: Gson的流式反序列化 Gson的流式序列化 使用GsonBuilder导出null值.格式化输出.日期时间及其它小功能 一.Gson的流式反序列化 自动方式 Gson提供了fromJson()和toJson() 两个直接用于解析和生成的方法,前者实现反序列化,后者实现了序列化.同时每个方法都提供了重载方法,我常用的

Java基础-String、StringBuffer、StringBuilder

看下面这段代码: public class Main { public static void main(String[] args) { String string = ""; for(int i=0;i<10000;i++){ string += "hello"; } } } 这句 string += "hello";的过程相当于将原有的string变量指向的对象内容取出与"hello"作字符串相加操作再存进另一个新

JavaString、StringBuilder、StringBuffer总结

背景: 最近项目中需要用到服务器模板和字符串拼接技术.服务器模板技术很多,JSP.Velocity.JDynamiTe等很多.字符串拼接技术在Java中更简单,StringBuilder.StringBuffer和重载的字符串"+"操作.但是实际开发中,发现自己平时对Java的字符串拼接的细节处理真的很差. 基础: 字符串操作主要问题在效率上,包括如下两点: (1)拼接效率. (2)查找.回溯效率. 凡是了解Java的都知道下面3点,而且这肯定是Java面试时必考的. (1)Strin

java中String和StringBuffer哪个效率高

大多数的网站以及多数的java书上都会说使用StringBuffer类进行字符串"连接"操作是比String类进行连接操作的效率高的,那么真的是这样吗?在这里我们实际自己测试一下,看看他们两个到底谁的效率高,然后从反编译的代码解释原因. 在我的这篇博客:<Java中 "abc" + '/'和"abc" + "/"的区别>中提到了String类的'+'操作是依赖于StringBuilder类的,而JDK API文档中

java面试题String,StringBuilder,StringBuffer

面试的经历中,相信大家都经常被问到这三者的区别,说到String我相信绝大多数的人都会说:"String是不可变的,final修饰的",你会看到面试官微微猥琐一笑,接着问到:"final修饰的类就是不可变的吗,那StringBuilder和StringBuffer不是final修饰的?" 1. 先来说说String 看下JDK1.7 String成员变量的源码 /** * @author Lee Boynton * @author Arthur van Hoff *

解析Java中的String、StringBuilder、StringBuffer类(一)

引言 String 类及其相关的StringBuilder.StringBuffer 类在 Java 中的使用相当的多,在各个公司的面试中也是必不可少的.因此,在本周,我打算花费一些时间来认真的研读一下 String.StringBuilder.StringBuffer类 的相关代码. String的不可变性 这个特性是 String 相当重要的一个特性,为了深入理解,我直接贴上其源代码 public String concat(String str) { int otherLen = str.

6-探秘Java中的String、StringBuilder以及StringBuffer

相信String这个类是Java中使用得最频繁的类之一,并且又是各大公司面试喜欢问到的地方,今天就来和大家一起学习一下String.StringBuilder和StringBuffer这几个类,分析它们的异同点以及了解各个类适用的场景.下面是本文的目录大纲: 一.你了解String类吗? 二.深入理解String.StringBuffer.StringBuilder 三.不同场景下三个类的性能测试 四.常见的关于String.StringBuffer的面试题(辟谣网上流传的一些曲解String类

String stringbuffer StringBuilder

最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下我读这些源码的收获吧.注意:虽然源码的版本是JDK6,但是个人觉得学习这个版本的源码对于理解数据结构非常有帮助,因为String就是一个数据结构,它是char []的封装,实现了很多对char []的操作 第一部分:String源码解析 (1)String实现了CharSequence接口,这个接口

StringBuilder、StringBuffer、String比较

1. String 类 String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间. String a = "a"; //假设a指向地址0x0001 a = "b";//重新赋值后a指向地址0x0002,但0x0001地址中保存的"a"依旧存在,但已经不再是a所指向的,a 已经指向了其它地址. 因此String的操作都是改变赋值地址而不是改变值操作. 2. StringBuf