利用反编译具体看看"+"的过程

1 public class Test
2 {
3 public static void main(String[] args)
4 {
5 int i=10;
6 String s="abc";
7 System.out.println (s+i);
8 }
9 }

可以看到的确是创建了StringBuilder类,然后调用了两次append方法,并且调用了toString方法。

查看AbstractStringBuilder源码可知:

s+i的过程可等价为s+String.valueOf(i),也可等价为new StringBuilder("abc").append(String.valueOf(i)).toString()

一直都很好奇,为什么String类要做成不可变的?以及它是怎么实现的?

对象不可变定义
不可变对象是指对象的状态在被初始化以后,在整个对象的生命周期内,不可改变。
如何不可变
通常情况下,在java中通过以下步骤实现不可变

  1. 对于属性不提供设值方法
  2. 所有的属性定义为private final
  3. 类声明为final不允许继承
  4. Return deep cloned objects with copied content for all mutable fields in class

注意:不用final关键字也可以实现对象不可变,使用final只是显示的声明,提示开发者和编译器为不可变。

Java中典型的不可变类为String类
为什么String被设计为不可变?

  1. 安全首要原因是安全,不仅仅体现在你的应用中,而且在JDK中,Java的类装载机制通过传递的参数(通常是类名)加载类,这些类名在类路径下,想象一下,假设String是可变的,一些人通过自定义类装载机制分分钟黑掉应用。如果没有了安全,Java不会走到今天
  2. 性能 string不可变的设计出于性能考虑,当然背后的原理是string pool,当然string pool不可能使string类不可变,不可变的string更好的提高性能。
  3. 线程安全 当多线程访问时,不可变对象是线程安全的,不需要什么高深的逻辑解释,如果对象不可变,线程也不能改变它。

参考:http://blog.csdn.net/topwqp/article/details/46380331

String类使用"+"来连接字符的整个过程描述

1)、大家经常会说不要使用"+" 来连接字符串这样效率不高(相对于 StringBuilder、StringBuffer)那为什么那,看看下面:

String  str= "a";  str=str+"b";  str=str+"c";

实现过程:

一、String  str= "a";创建一个String对象,str 引用到这个对象。

二、再创建一个长度为str.length() 的StringBuffer 对象。

三、StringBuffer  strb=new  StringBuffer(str)。

四、调用StringBuffer的append()方法将”b“添加进去,strb.append("b")。

五、调用strb 的toString()方法创建String对象,之前对象失去引用而str重新引用到这个新对 象。

六、同样在创建StringBuffer对象 调用append()方法将”c“添加进去,调用toString() 方法 创建String对象。

七、再将strb引用到 新创建的String对象。之前对象失去引用之后存放在常量池中,等待垃圾回收。

看到上面使用“+”连接字符串的过程,就明白了为什么要使用StringBuffer 来连接字符而不是使用String 的“+”来连接。

(2)、知道了使用”+“连接的过程,我们再来看看上面提到的使用”+“号为什么会创建新的对象,也就是说String对象是不可变对象。这里有个概念就是对象不可变,而String 的对象就是一个不可变对象。那什么叫对象不可变那: 当一个对象创建完成之后,不能再修改他的状态,不能改变状态是指不能改变对象内的成员变量,包括基本数据类型的值不能改变。引用类型的变量不能指向其他对象,引用类型指向的对象的状态也不能改变。对象一旦创建就没办法修改期所有属性,所以要修改不可变对象的值就只能重新创建对象。

String对象不可变源码分析

(1)、上面说了String 对象为不可变对象,为什么String 对象不可变,String对象的状态不能改变,接下来我们看看String 类的源码:

jdk 1.7 的源码

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    // 数组的被final修饰,所以数据引用变量的值不能变

private final char value[];
    /** Cache the hash code for the string */

//  缓存String对象的哈希值

private int hash; // Default to 0

我们会发现String 的底层是使用字符数组来实现的。

String 类中只有两个成员变量一个是value 一个是hash,这个hash和我们讨论的问题没关系,通过注解我们知道他是缓存String对象的hash值

value 是一个被final修饰的数组对象,所以只能说他不能再引用到其他对象而不能说明他所引用的对象的内容不能改变。但我们在往下看源码就会发现String 类没有给这两个成员变量提供任何的方法所以我们也没办法修改所引用对象的内容,所以String 对象一旦被创建,这个变量被初始化后就不能再修改了,所以说String 对象是不可变对象。

(2)、String 对象不是提供了像replace()等方法可以修改内容的吗,其实这个方法内部创建了一个新String 对象 在把这个新对象重新赋值给了引用变量,看看源码你就相信了他是在内部重现创建了String 对象

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }

// 创建新对象

return new String(buf, true);
            }
        }
        return this;
    }

总结:

1、String 类是一个final 修饰的类所以这个类是不能继承的,也就没有子类。

2、String 类的成员变量都是final类型的并且没有提供任何方法可以来修改引用变量所引用的对象的内容,所以一旦这个对象被创建并且成员变量初始化后这个对象就不能再改变了,所以说String 对象是一个不可变对象。

3、使用“+”连接字符串的过程产生了很多String 对象和StringBuffer 对象所以效率相比直接使用StringBuffer 对象的append() 方法来连接字符效率低很多。

4、引用变量是存在java虚拟机栈内存中的,它里面存放的不是对象,而是对象的地址或句柄地址。

5、对象是存在java heap(堆内存)中的值

6、引用变量的值改变指的是栈内存中的这个引用变量的值的改变是,对象地址的改变或句柄地址的改变,而对象的改变指的是存放在Java heap(堆内存)中的对象内容的改变和引用变量的地址和句柄没有关系。

StringBuffer

我们在上面说String对象是不可变的,而StringBuffer 对象是可变的,大家都说在能大体了解字符串的长度的情况下创建StringBuffer对象时 指定其容量,在上面的string中我们也知道使用“+”号的时候我们也是调用了append方法。

1、 为什么StringBuffer 对象可变, 为什么要尽量指定初始大小,append方法是怎么实现的 下面我们来看看这几个为什么

2、String 对象不可变是因为成员变量都被final修饰并且没有提供任何访问被引用对象的方法所以不能改变,而StringBuffer是怎么样的那我们可以去看看源码:

(1)、public final class StringBuffer  extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

/** use serialVersionUID from JDK 1.0.2 for interoperability */
    static final long serialVersionUID = 3388685877147921107L;

/**
     * Constructs a string buffer with no characters in it and an
     * initial capacity of 16 characters.
     */

//  默认为16个字符
    public StringBuffer() {
        super(16);
    }

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

/**
     * Constructs a string buffer initialized to the contents of the
     * specified string. The initial capacity of the string buffer is
     * <code>16</code> plus the length of the string argument.
     *
     * @param   str   the initial contents of the buffer.
     * @exception NullPointerException if <code>str</code> is <code>null</code>
     */
    public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }

3、StringBuffer 类继承自AbstractStringBuilder 那在看看AbstractStringBuilder的源码

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */

// 这里我们看到,这个数组没有被final 修饰,所以引用变量的值可以改变,

//可以引用到其他数组对象

char[] value;

/**
     * The count is the number of characters used.
     */
   //  记录字符的个数

int count;

/**
     * This no-arg constructor is necessary for serialization of subclasses.
     */
    AbstractStringBuilder() {
    }

/**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {

// 构造函数,创建数组对象

value = new char[capacity];
    }

/**
     * Returns the length (character count).
     *
     * @return  the length of the sequence of characters currently
     *          represented by this object
     */
    public int length() {
        return count;
    }

从这些源码我们看到 他的数组和String 的不一样,因为成员变量value数组没有被final修饰所以可以修改他的引用变量的值,即可以引用到新的数组对象。所以StringBuffer对象是可变的

3、如果知道字符串的长度则创建对象的时候尽量指定大小

(1)、在上面的源代码中我们看到StringBuffer 的构造函数默认创建的大小为16个字符。

(2)、如果我们在创建对象的时候指定了大小则创建指定容量大小的数组对象

// 调用父类的构造函数,创建数组对象

public StringBuffer(int capacity) {
        super(capacity);
    }

/**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
     //按照指定容量创建字符数组

value = new char[capacity];
    }

(3)、如果在创建对象时构造函数的参数为字符串则 创建的数组的长度为字符长度+16字符

这样的长度,然后再将这个字符串添加到字符数组中,添加的时候会判断原来字符数组中的个数加上这个字符串 的长度是否大于这个字符数组的大小如果大于则进行扩容如果没有则添加,源码如下:

public StringBuffer(String str) {
        super(str.length() + 16);
        append(str);
    }
append 出现在了这里刚好一起来看看 append方法的实现

4、其实append方法就做两件事,如果 count (字符数组中已有字符的个数)加添加的字符串的长度小于 value.length 也就是小于字符数组的容量则直接将要添加的字符拷贝到数组在修改count就可以了。

5、如果cout和添加的字符串的长度的和大于value.length  则会创建一个新字符数组 再将原有的字符拷贝到新字符数组,再将要添加的字符添加到字符数组中,再改变conut(字符数组中字符的个数)

整个添加过程的源码如下

public synchronized StringBuffer append(Object obj) {
        super.append(String.valueOf(obj));
        return this;
    }

调用父类的方法
 public AbstractStringBuilder append(Object obj) {
        return append(String.valueOf(obj));
    }

这个方法中调用了ensureCapacityInternal ()方法判断count(字符数组原有的字符个数)+str.length() 的长度是否大于value容量
/**
     * This method has the same contract as ensureCapacity, but is
     * never synchronized.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

如果count+str.length() 长度大于value的容量 则调用方法进行扩容
  /**
     * This implements the expansion semantics of ensureCapacity with no
     * size check or synchronization.
     */
    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);
    }

Arrays.copyOf(value,newCapacity) 复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。

上面的getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)   将字符从此字符串复制到目标字符数组dst中,第一个参数 第二个参数截取要添加字符串的长度,第三个为目标字符数组第四个为字符串要添加到数组的开始位置

到这里数组的赋值都结束了,修改count的值,整个append也就结束了。

总结:

1、StringBuffer 类被final 修饰所以不能继承没有子类

2、StringBuffer 对象是可变对象,因为父类的 value [] char 没有被final修饰所以可以进行引用的改变,而且还提供了方法可以修改被引用对象的内容即修改了数组内容。

3、在使用StringBuffer对象的时候尽量指定大小这样会减少扩容的次数,也就是会减少创建字符数组对象的次数和数据复制的次数,当然效率也会提升。

StringBuilder 和StringBuffer 很像只是不是线程安全的其他的很像所以不罗嗦了。

参考:http://blog.csdn.net/qh_java/article/details/46382265

String、StringBuffer、StringBuilder源码分析的更多相关文章

  1. String,StringBuffer,StringBuilder源码分析

    1.类结构 String Diagrams StringBuffer Diagrams StringBuilder Diagrams 通过以上Diagrams可以看出,String,StringBuf ...

  2. [源码]String StringBuffer StringBudlider(2)StringBuffer StringBuilder源码分析

      纵骑横飞 章仕烜   昨天比较忙 今天把StringBuffer StringBulider的源码分析 献上   在讲 StringBuffer StringBuilder 之前 ,我们先看一下 ...

  3. JDK中String类的源码分析(二)

    1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 public boolean s ...

  4. String类的源码分析

    之前面试的时候被问到有没有看过String类的源码,楼主当时就慌了,回来赶紧补一课. 1.构造器(构造方法) String类提供了很多不同的构造器,分别对应了不同的字符串初始化方法,此处从源码中摘录如 ...

  5. StringBuffer 和Stringbuilder源码分析

    首先看一下他们的继承关系   这个两个对象都继承了AbstractStringBuilder抽象类.   1.他们的实现方式都一样的,唯一区别的StringBuffer在多线程的时候是保证了数据安全, ...

  6. java.lang.StringBuilder源码分析

    StringBuilder是一个可变序列的字符数组对象,它继承自AbstractStringBuilder抽象类.它不保证同步,设计出来的目的是当这个字符串缓存只有单线程使用的时候,取代StringB ...

  7. JDK中String类的源码分析(一)

    1.String类是final的,不允许被继承 /** The value is used for character storage. */ private final char value[]; ...

  8. Stringbuffer与Stringbuilder源码学习和对比

    >>String/StringBuffer/StringBuilder的异同 (1)相同点观察源码会发现,三个类都是被final修饰的,是不可被继承的.(2)不同点String的对象是不可 ...

  9. Koa源码分析(二) -- co的实现

    Abstract 本系列是关于Koa框架的文章,目前关注版本是Koa v1.主要分为以下几个方面: Koa源码分析(一) -- generator Koa源码分析(二) -- co的实现 Koa源码分 ...

随机推荐

  1. Android 的图片异步请求加三级缓存 ACE

    使用xUtils等框架是很方便,但今天要用代码实现bitmapUtils 的功能,很简单, 1 AsyncTask请求一张图片 ####AsyncTask #####AsyncTask是线程池+han ...

  2. 使用LocalBroadcastManager

    Android中BroadcastReceiver主要用途有 发送通知,更新UI或者数据,应用程序间相互通信,监听系统状态(比如开机,网络等) Android中BroadcasetReceiver的注 ...

  3. LINQ基础概述

    介绍LINQ基础之前,首说一下LINQ 的历史和LINQ是什么,然后说一下学习 LINQ要了解的东西和 LINQ基础语法   LINQ 的历史 从语言方面的进化 –委托 –匿名方法 –Lambda表达 ...

  4. VC维含义

    VC维含义的个人理解 有关于VC维可以在很多机器学习的理论中见到,它是一个重要的概念.在读<神经网络原理>的时候对一个实例不是很明白,通过这段时间观看斯坦福的机器学习公开课及相关补充材料, ...

  5. android服务之启动方式

    服务有两种启动方式 通过startService方法来启动 通过bindService来开启服务 布局文件 在布局文件中我们定义了四个按键来测试这两种方式来开启服务的不同 <?xml versi ...

  6. keypress,keydown,keyup,charCode,keyCode兼容性问题

    keypress对应的是字符编码,如“ABC”,“123”之类,有大小写之分(有兼容性问题) keydown,keyup对应的是键盘的键码,无大小写之分,每个键盘都有一个键码(无兼容性问题) 使用ke ...

  7. 68 id -显示用户的id

    Linux id命令用于显示用户的ID,以及所属群组的ID. id会显示用户以及所属群组的实际与有效ID.若两个ID相同,则仅显示实际ID.若仅指定用户名称,则显示目前用户的ID. 语法 id [-g ...

  8. 34-nl 简明笔记

    为文本文件添加行号 nl [options] files 参数 files是nl需要为其添加行号的文本文件路径名,如果有多个文件,则nl会把多个文件合在一起编号,并输出到标准输出上 选项 -b     ...

  9. “CEPH浅析”系列之八——小结

    最初决定写这些文章的时候,本打算大致记录一下,几千字也就了事了.可是越写越觉得东西多,不说明白总有些不甘心,于是就越写越长,到这儿为止貌似已经有一万七千多字了.除了博士论文之外,应该是没有写过更长的东 ...

  10. mycat 9066管理端口 常用命令

    1.连接mycat 9066管理端口 命令:mysql -uroot -proot -P9066 -h127.0.0.1 -u:用户名 -p:密码 -P:端口 -h:ip地址例:linux路径切换到m ...