stringbuilder 订阅
StringBuilder是一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。 展开全文
StringBuilder是一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。
信息
所属学科
IT
外文名
StringBuilder
所属科目
C#
所属领域
程序编写
来    源
jdk1.5及以后的版本
中文名
一个可变的字符序列
StringBuilder简介
如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。 在 StringBuilder 上的主要操作是 append 和 insert 方法。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。 例如,如果 z 引用一个当前内容为“start”的字符串生成器对象,则该方法调用 z.append("le") 将使字符串生成器包含“startle”,而 z.insert(4, "le") 将更改字符串生成器,使之包含“starlet”。 通常,如果 n引用 StringBuilder 的实例(StringBuilder n = new StringBuilder();),则 n.append(x) 和 n.insert(n.length(), x) 具有相同的效果。 每个字符串生成器都有一定的容量。只要字符串生成器所包含的字符序列的长度没有超出此容量,就无需分配新的内部缓冲区。如果内部缓冲区溢出,则此容量自动增大。 将StringBuilder的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用StringBuffer。 [1]  StringBuilder类可以用于在无需创建一个新的字符串对象情况下修改字符串。StringBuilder不是线程安全的,而StringBuffer是线程安全的。但StringBuilder在单线程中的性能比StringBuffer高。
收起全文
精华内容
下载资源
问答
  • StringBuilder

    万次阅读 2019-12-07 14:22:53
    StringBuilder线程不安全? 在分析这个问题之前我们要知道StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final修饰的,是不可变的...

    StringBuilder线程不安全?

     

    在分析这个问题之前我们要知道StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final修饰的,是不可变的,而StringBuilder和StringBuffer的char数组是可变的。

    首先通过一段代码去看一下多线程操作StringBuilder对象会出现什么问题

    public class StringBuilderDemo {
    
    
    
        public static void main(String[] args) throws InterruptedException {
    
            StringBuilder stringBuilder = new StringBuilder();
    
            for (int i = 0; i < 10; i++){
    
                new Thread(new Runnable() {
    
                    @Override
    
                    public void run() {
    
                        for (int j = 0; j < 1000; j++){
    
                            stringBuilder.append("a");
    
                        }
    
                    }
    
                }).start();
    
            }
    
    
    
            Thread.sleep(100);
    
            System.out.println(stringBuilder.length());
    
        }
    
    
    
    }

    我们能看到这段代码创建了10个线程,每个线程循环1000次往StringBuilder对象里面append字符。正常情况下代码应该输出10000,但是实际运行会输出什么呢?

    我们看到输出了“9326”,小于预期的10000,并且还抛出了一个ArrayIndexOutOfBoundsException异常(异常不是必现)。

    1、为什么输出值跟预期值不一样

    我们先看一下StringBuilder的两个成员变量(这两个成员变量实际上是定义在AbstractStringBuilder里面的,StringBuilder和StringBuffer都继承了AbstractStringBuilder)

    //存储字符串的具体内容

    char[] value;

    //已经使用的字符数组的数量

    int count;

    再看StringBuilder的append()方法:

    @Override
    
    public StringBuilder append(String str) {
    
        super.append(str);
    
        return this;
    
    }
    
    StringBuilder的append()方法调用的父类AbstractStringBuilder的append()方法
    
    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 += len不是一个原子操作。假设这个时候count值为10,len值为1,两个线程同时执行到了第七行,拿到的count值都是10,执行完加法运算后将结果赋值给count,所以两个线程执行完后count值为11,而不是12。这就是为什么测试代码输出的值要比10000小的原因。

    2、为什么会抛出ArrayIndexOutOfBoundsException异常。

    我们看回AbstractStringBuilder的append()方法源码的第五行,ensureCapacityInternal()方法是检查StringBuilder对象的原char数组的容量能不能盛下新的字符串,如果盛不下就调用expandCapacity()方法对char数组进行扩容。

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

    扩容的逻辑就是new一个新的char数组,新的char数组的容量是原来char数组的两倍再加2,再通过System.arryCopy()函数将原数组的内容复制到新数组,最后将指针指向新的char数组。

    void expandCapacity(int minimumCapacity) {
    
        //计算新的容量
    
        int newCapacity = value.length * 2 + 2;
    
        //中间省略了一些检查逻辑
    
        ...
    
        value = Arrays.copyOf(value, newCapacity);
    
    }
    
    Arrys.copyOf()方法
    
    public static char[] copyOf(char[] original, int newLength) {
    
        char[] copy = new char[newLength];
    
        //拷贝数组
    
        System.arraycopy(original, 0, copy, 0,
    
                             Math.min(original.length, newLength));
    
        return copy;
    
    }

    AbstractStringBuilder的append()方法源码的第六行,是将String对象里面char数组里面的内容拷贝到StringBuilder对象的char数组里面,代码如下:

    str.getChars(0, len, value, count);
    
    getChars()方法
    
    public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    
        //中间省略了一些检查
    
        ...   
    
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    
        }

    拷贝流程见下图

    假设现在有两个线程同时执行了StringBuilder的append()方法,两个线程都执行完了第五行的ensureCapacityInternal()方法,此刻count=5。

    这个时候线程1的cpu时间片用完了,线程2继续执行。线程2执行完整个append()方法后count变成6了

    线程1继续执行第六行的str.getChars()方法的时候拿到的count值就是6了,执行char数组拷贝的时候就会抛出ArrayIndexOutOfBoundsException异常。

    至此,StringBuilder为什么不安全已经分析完了。如果我们将测试代码的StringBuilder对象换成StringBuffer对象会输出什么呢?

    当然是输出10000

    展开全文
  • stringbuilder

    2020-02-29 21:16:44
    StringBuilder 类是可变的。它是 String 的对等类,它可以增加和编写字符的可变序列,并且能够将字符插入到字符串中间或附加到字符串末尾(当然是不用创建其他对象的) StringBuilder 的构造方法: 构造方法 说明 ...

    StringBuilder 类是可变的。它是 String 的对等类,它可以增加和编写字符的可变序列,并且能够将字符插入到字符串中间或附加到字符串末尾(当然是不用创建其他对象的)

    StringBuilder 的构造方法:

    构造方法 说明
    StringBuilder() 构造一个其中不带字符的 StringBuilder,其初始容量为 16 个字符
    StringBuilder(CharSequence seq) 构造一个 StringBuilder,它包含与指定的 CharSequence 相同的字符
    StringBuilder(int capacity) 构造一个具有指定初始容量的 StringBuilder
    StringBuilder(String str) 并将其内容初始化为指定的字符串内容
    StringBuilder 类的常用方法:

    方法 返回值 功能描述
    insert(int offsetm,Object obj) StringBuilder 在 offsetm 的位置插入字符串 obj
    append(Object obj) StringBuilder 在字符串末尾追加字符串 obj
    length() int 确定 StringBuilder 对象的长度
    setCharAt(int index,char ch) void 使用 ch 指定的新值设置 index 指定的位置上的字符
    toString() String 转换为字符串形式
    reverse() StringBuilder 反转字符串
    delete(int start, int end) StringBuilder 删除调用对象中从 start 位置开始直到 end 指定的索引(end-1)位置的字符序列
    replace(int start, int end, String str) StringBuilder 使用一组字符替换另一组字符。将用替换字符串从 start 指定的位置开始替换,直到 end 指定的位置结束
    上面的方法中我们选择几个,来写写代码吧:

    在/home/project/目录下新建StringBuilderTest.java

    public class StringBuilderTest {

    public static void main(String[] args){
        //定义和初始化一个StringBuilder类的字串s
        StringBuilder s = new StringBuilder("I");
        //在s后面添加字串" java"
        s.append(" java");
        //在s[1]的位置插入字串
        s.insert(1, " love");
        String t = s.toString(); //转为字符串
        System.out.println(t);
    }
    

    }

    展开全文
  • Stringbuilder

    2013-09-01 12:10:49
    StringBuilder和StringBuffer String的长度是固定不变的,你不能改变它的内容,任何对String的修改都将产生新的String对象。因此,如果你的代码对字符串的操作频繁的话,那么对于系统来说就是一笔很大的开销。所以...

    StringBuilderStringBuffer

    String的长度是固定不变的,你不能改变它的内容,任何对String的修改都将产生新的String对象。因此,如果你的代码对字符串的操作频繁的话,那么对于系统来说就是一笔很大的开销。所以在java 5.0推出了StringBuilder,这个类默认初始16个字符的长度,你也可以自行设置长度,如果字符的长度超出了可容纳的长度,它可以自动增加长度。

    大家都知道HashTable是线程安全的,而HashMap是线程不安全的,但是我们在单线程中经常使用HashMap,而不使用HashTable,因为它新增了内容,势必会花开销去维护这新增的内容,因此在单线程中,它不仅没占优势,想法,还处于弱势。同理,我们的java在推出了stringbuilder之后又退出了stringbuffer,区别跟上面类似,Stringbuilder是线程不安全的,所以它在单线程的效率要比Stringbuffer要高,但在syncronized中最好还是使用线程安全的类。虽然这样,但这俩个类比String类的效率仍旧是高的多,所以这两个类是作为string的取代类而存在的。

    当然,如果你有兴趣,你可以试一下,每个String添加1000条数据,每次开始前打印当前时间(system.currentTimeMillis()),然后结束再打印一下,从而查看这三者时间的差距,你就可以得出同样的结论。

    它的主要操作有appendinsert方法,可重载以接收任意类型的数据,append是添加到字符串的末尾,而insert是指定位置插入。

    下面就是基本使用方法,Stringbuffer类似,他们只有线程安全不安全的区别,其余的都是差不多的。

    public class stringbuilder01 {

    public static void main(String[] args) {

    StringBuilder stringbuilder=new StringBuilder();

    //StringBuilder.append(obj)实现将一个对象作为字符存入,这里面还可以是8中数据类型、string和stringbuilder对象

    stringbuilder.append("123");

    //stringbuilder.charAt(index)获得位置在index索引的char字符值

    stringbuilder.charAt(0);

    //stringbuilder.indexOf("string")获得某个字符串第一次出现的索引

    stringbuilder.indexOf("12");

    //最后一次出现的索引stringbuilder.lastIndexOf("string");

    stringbuilder.lastIndexOf("12");

    //stringbuilder.insert(index,T)可以在index的位置,插入8中基本数据类型或者char数组

    stringbuilder.insert(0,"123");

    //stringbuilder.delete(start, end)删除某一段内容

    //stringbuilder.replace(begin, end, String);将字符中begin开始到end的位置使用string替换

    stringbuilder.replace(0, 1, "hello");

    }

    }

    展开全文
  • String、StringBuffer与StringBuilder之间区别

    万次阅读 多人点赞 2019-05-08 18:25:33
    StringBuilder String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间 StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的...

     

    String     StringBuffer     StringBuilder
    String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间  StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量  可变类,速度更快
    不可变     可变    可变
       线程安全     线程不安全
       多线程操作字符串    单线程操作字符串

    一、Java String 类——String字符串常量

        简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,这样不仅效率低下,而且大量浪费有限的内存空间,所以经常改变内容的字符串最好不要用 String 。因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

    我们来看一下这张对String操作时内存变化的图:

          我们可以看到,初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费。为了应对经常性的字符串相关的操作,就需要使用Java提供的其他两个操作字符串的类——StringBuffer类和StringBuild类来对此种变化字符串进行处理。

    二、StringBuffer 和 StringBuilder 类——StringBuffer、StringBuilder字符串变量

    StringBuffer 字符串变量(线程安全)
    StringBuilder 字符串变量(非线程安
     

    对字符串进行修改的时候,特别是字符串对象经常改变的情况下,需要使用 StringBuffer 和 StringBuilder 类。

    和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象

    StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

    由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

    三者的继承结构

    三者的区别

    (1)字符修改上的区别(主要,见上面分析)

    (2)初始化上的区别,String可以空赋值,后者不行,报错

    ①String

    String s = null;   

    String s = “abc”;   

    ②StringBuffer

    StringBuffer s = null; //结果警告:Null pointer access: The variable result can only be null at this location

    StringBuffer s = new StringBuffer();//StringBuffer对象是一个空的对象

    StringBuffer s = new StringBuffer(“abc”);//创建带有内容的StringBuffer对象,对象的内容就是字符串”

    小结:(1)如果要操作少量的数据用 String;

    (2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;

    (3)单线程操作字符串缓冲区下操作大量数据 StringBuilder

     

        而在某些特别情况下, String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
     String S1 = "This is only a" + " simple" + " test";
     StringBuffer Sb = new StringBuffer("This is only a").append(" simple").append(" test");
     你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
     String S1 = “This is only a” + “ simple” + “test”; 其实就是:
     String S1 = “This is only a simple test”;  

    所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
    String S2 = “This is only a”;
    String S3 = “ simple”;
    String S4 = “ test”;
    String S1 = S2 +S3 + S4;
    这时候 JVM 会规规矩矩的按照原来的方式去做


    在大部分情况下 StringBuffer > String
    StringBuffer
    Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
    可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
    StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
    例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
    在大部分情况下 StringBuilder > StringBuffer

    java.lang.StringBuilder
    java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。

    作者:每次上网冲杯Java时,都能看到关于String无休无止的争论。还是觉得有必要让这个讨厌又很可爱的String美眉,赤裸裸的站在我们这些Java色狼面前了。嘿嘿....

    众所周知,String是由字符组成的串,在程序中使用频率很高。Java中的String是一个类,而并非基本数据类型。 不过她却不是普通的类哦!!!

     

    【镜头1】 String对象的创建 
          1、关于类对象的创建,很普通的一种方式就是利用构造器,String类也不例外:String s=new String("Hello world"); 问题是参数"Hello world"是什么东西,也是字符串对象吗?莫非用字符串对象创建一个字符串对象?

          2、当然,String类对象还有一种大家都很喜欢的创建方式:String s="Hello world"; 但是有点怪呀,怎么与基本数据类型的赋值操作(int i=1)很像呀?

          在开始解释这些问题之前,我们先引入一些必要的知识:

    ★ Java class文件结构 和常量池 
          我们都知道,Java程序要运行,首先需要编译器将源代码文件编译成字节码文件(也就是.class文件)。然后在由JVM解释执行。
          class文件是8位字节的二进制流 。这些二进制流的涵义由一些紧凑的有意义的项组成。比如class字节流中最开始的4个字节组成的项叫做魔数 (magic),其意义在于分辨class文件(值为0xCAFEBABE)与非class文件。class字节流大致结构如下图左侧。

                                    

          其中,在class文件中有一个非常重要的项——常量池 。这个常量池专门放置源代码中的符号信息(并且不同的符号信息放置在不同标志的常量表中)。如上图右侧是HelloWorld代码中的常量表(HelloWorld代码如下),其中有四个不同类型的常量表(四个不同的常量池入口)。关于常量池的具体细节,请参照我的博客《Class文件内容及常量池 》

    Java代码  

    public class HelloWorld{  
        void hello(){  
            System.out.println("Hello world");  
        }  
    }  

          通过上图可见,代码中的"Hello world"字符串字面值被编译之后,可以清楚的看到存放在了class常量池中的字符串常量表中(上图右侧红框区域)。

     

    ★ JVM运行class文件

          源代码编译成class文件之后,JVM就要运行这个class文件。它首先会用类装载器加载进class文件。然后需要创建许多内存数据结构来存放class文件中的字节数据。比如class文件对应的类信息数据、常量池结构、方法中的二进制指令序列、类方法与字段的描述信息等等。当然,在运行的时候,还需要为方法创建栈帧等。这么多的内存结构当然需要管理,JVM会把这些东西都组织到几个“运行时数据区 ”中。这里面就有我们经常说的“方法区 ”、“堆 ”、“Java栈 ”等。详细请参见我的博客《Java 虚拟机体系结构 》 。

          上面我们提到了,在Java源代码中的每一个字面值字符串,都会在编译成class文件阶段,形成标志号 为8(CONSTANT_String_info)的常量表 。 当JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中。同时JVM会自动为CONSTANT_String_info常量表中 的字符串常量字面值 在堆中 创建 新的String对象(intern字符串 对象,又叫拘留字符串对象)。然后把CONSTANT_String_info常量表的入口地址转变成这个堆中String对象的直接地址(常量池解 析)。 

          这里很关键的就是这个拘留字符串对象 。源代码中所有相同字面值的字符串常量只可能建立唯一一个拘留字符串对象。 实际上JVM是通过一个记录了拘留字符串引用的内部数据结构来维持这一特性的。在Java程序中,可以调用String的intern()方法来使得一个常规字符串对象成为拘留字符串对象。我们会在后面介绍这个方法的。

     

    ★ 操作码助忆符指令 
          有了上面阐述的两个知识前提,下面我们将根据二进制指令来区别两种字符串对象的创建方式:  

          (1) String s=new String("Hello world");编译成class文件后的指令(在myeclipse中查看):

    Class字节码指令集代码

     

    0  new java.lang.String [15]  //在堆中分配一个String类对象的空间,并将该对象的地址堆入操作数栈。  
    3  dup //复制操作数栈顶数据,并压入操作数栈。该指令使得操作数栈中有两个String对象的引用值。  
    4  ldc <String "Hello world"> [17] //将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈  
    6  invokespecial java.lang.String(java.lang.String) [19] //调用String的初始化方法,弹出操作数栈栈顶的两个对象地址,用拘留String对象的值初始化new指令创建的String对象,然后将这个对象的引用压入操作数栈  
    9  astore_1 [s] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是new指令创建出的,已经被初始化的String对象的地址 (此时的栈顶值弹出存入局部变量中去)。  

    注意:
    【这里有个dup指令。其作用就是复制之前分配的Java.lang.String空间的引用并压入栈顶。那么这里为什么需要这样么做呢?因为invokespecial指令通过[15]这个常量池入口寻找到了java.lang.String()构造方法,构造方法虽然找到了。但是必须还得知道是谁的构造方法,所以要将之前分配的空间的应用压入栈顶让invokespecial命令应用才知道原来这个构造方法是刚才创建的那个引用的,调用完成之后将栈顶的值弹出。之后调用astore_1将此时的栈顶值弹出存入局部变量中去。】     


     事实上,在运行这段指令之前,JVM就已经为"Hello world"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址。 大家注意了,此时在JVM管理的堆中,有两个相同字符串值的String对象:一个是拘留字符串对象,一个是new新建的字符串对象。如果还有一条创建语句String s1=new String("Hello world");堆中有几个值为"Hello world"的字符串呢? 答案是3个,大家好好想想为什么吧!

     

          (2)将String s="Hello world";编译成class文件后的指令:

    Class字节码指令集代码  

    0  ldc <String "Hello world"> [15]//将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈  
    2  astore_1 [str] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是拘留字符串对象在堆中的地址  

         和上面的创建指令有很大的不同,局部变量s存储的是早已创建好的拘留字符串的堆地址(没有new 的对象了)。 大家好好想想,如果还有一条穿件语句String s1="Hello word";此时堆中有几个值为"Hello world"的字符串呢?答案是1个。那么局部变量s与s1存储的地址是否相同呢?  呵呵, 这个你应该知道了吧。

     

    ★ 镜头总结: String类型脱光了其实也很普通。真正让她神秘的原因就在于CONSTANT_String_info常量表 拘留字符串对象 的存在。现在我们可以解决江湖上的许多纷争了。

       纷争1】关于字符串相等关系的争论

    Java代码 

    //代码1  
    String sa=new String("Hello world");            
    String sb=new String("Hello world");      
    System.out.println(sa==sb);  // false       
    //代码2    
    String sc="Hello world";    
    String sd="Hello world";  
    System.out.println(sc==sd);  // true   

           代码1中局部变量sa,sb中存储的是JVM在堆中new出来的两个String对象的内存地址。虽然这两个String对象的值(char[]存放的字符序列)都是"Hello world"。 因此"=="比较的是两个不同的堆地址。代码2中局部变量sc,sd中存储的也是地址,但却都是常量池中"Hello world"指向的堆的唯一的那个拘留字符串对象的地址 。自然相等了。

      【纷争2】 字符串“+”操作的内幕

    Java代码  

    //代码1  
    String sa = "ab";                                          
    String sb = "cd";                                       
    String sab=sa+sb;                                      
    String s="abcd";  
    System.out.println(sab==s); // false  
    //代码2  
    String sc="ab"+"cd";  
    String sd="abcd";  
    System.out.println(sc==sd); //true  

        代码1中局部变量sa,sb存储的是堆中两个拘留字符串对象的地址。而当执行sa+sb时,JVM首先会在堆中创建一个StringBuilder类,同时用sa指向的拘留字符串对象完成初始化,然后调用append方法完成对sb所指向的拘留字符串的合并操作,接着调用StringBuilder的toString()方法在堆中创建一个String对象,最后将刚生成的String对象的堆地址存放在局部变量sab中。而局部变量s存储的是常量池中"abcd"所对应的拘留字符串对象的地址。 sab与s地址当然不一样了。这里要注意了,代码1的堆中实际上有五个字符串对象:三个拘留字符串对象、一个String对象和一个StringBuilder对象。
          代码2中"ab"+"cd"会直接在编译期就合并成常量"abcd", 因此相同字面值常量"abcd"所对应的是同一个拘留字符串对象,自然地址也就相同。

     

    【镜头二】  String三姐妹(String,StringBuffer,StringBuilder) 
            String扒的差不多了。但他还有两个妹妹StringBuffer,StringBuilder长的也不错哦!我们也要下手了:
                               String(大姐,出生于JDK1.0时代)          不可变字符序列
                               StringBuffer(二姐,出生于JDK1.0时代)    线程安全的可变字符序列
                               StringBuilder(小妹,出生于JDK1.5时代)   非线程安全的可变字符序列 

     

    ★StringBuffer与String的可变性问题。 
             我们先看看这两个类的部分源代码:

    Java代码  

    //String   
    public final class String  
    {  
            private final char value[];  
      
             public String(String original) {  
                  // 把原字符串original切分成字符数组并赋给value[];  
             }  
    }  
      
    //StringBuffer   
    public final class StringBuffer extends AbstractStringBuilder  
    {  
             char value[]; //继承了父类AbstractStringBuilder中的value[]  
             public StringBuffer(String str) {  
                     super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组  
                     append(str); //将str切分成字符序列并加入到value[]中  
            }  
    }  

         很显然,String和StringBuffer中的value[]都用于存储字符序列。但是,
          (1) String中的是常量(final)数组,只能被赋值一次。 
          比如:new String("abc")使得value[]={'a','b','c'}(查看jdk String 就是这么实现的),之后这个String对象中的value[]再也不能改变了。这也正是大家常说的,String是不可变的原因 。    
          注意:这个对初学者来说有个误区,有人说String str1=new String("abc"); str1=new String("cba");不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别。这里我简单的说明一下,对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段)。而对象引用指的是堆中对象本身所存放的地址,一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据。


          (2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。

          比如:new StringBuffer("abc")使得value[]={'a','b','c','',''...}(注意构造的长度是str.length()+16)。如果再将这个对象append("abc"),那么这个对象中的value[]={'a','b','c','a','b','c',''....}。这也就是为什么大家说 StringBuffer是可变字符串 的涵义了。从这一点也可以看出,StringBuffer中的value[]完全可以作为字符串的缓冲区功能。其累加性能是很不错的,在后面我们会进行比较。

         总结,讨论String和StringBuffer可不可变。本质上是指对象中的value[]字符数组可不可变,而不是对象引用可不可变。 

     

    ★StringBuffer与StringBuilder的线程安全性问题 
          StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
          有多线程编程经验的程序员应该知道synchronized。这个关键字是为线程同步机制 设定的。我简要阐述一下synchronized的含义:
          每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方法。这就是解决线程同步问题的锁机制。 
          了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffer比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
          注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢? 

     

    ★String和StringBuffer的效率问题(这可是个热门话题呀!) 
          首先说明一点:StringBuffer和StringBuilder可谓双胞胎,StringBuilder是1.5新引入的,其前身就是StringBuffer。StringBuilder的效率比StringBuffer稍高,如果不考虑线程安全,StringBuilder应该是首选。另外,JVM运行程序主要的时间耗费是在创建对象和回收对象上。
          我们用下面的代码运行1W次字符串的连接操作,测试String,StringBuffer所运行的时间。

    Java代码  

    //测试代码  
    public class RunTime{  
        public static void main(String[] args){  
               ● 测试代码位置1  
              long beginTime=System.currentTimeMillis();  
              for(int i=0;i<10000;i++){  
                     ● 测试代码位置2  
              }  
              long endTime=System.currentTimeMillis();  
              System.out.println(endTime-beginTime);     
        }  
    }  

    (1) String常量与String变量的"+"操作比较 
            ▲测试①代码:     (测试代码位置1)  String str="";
                                      (测试代码位置2)  str="Heart"+"Raid";
                [耗时:  0ms]
                 
           ▲测试②代码        (测试代码位置1)  String s1="Heart";
                                                               String s2="Raid";
                                                               String str="";
                                      (测试代码位置2)  str=s1+s2;
                [耗时:  15—16ms]
          结论:String常量的“+连接”  稍优于  String变量的“+连接”。 
          原因:测试①的"Heart"+"Raid"在编译阶段就已经连接起来,形成了一个字符串常量"HeartRaid",并指向堆中的拘留字符串对象。运行时只需要将"HeartRaid"指向的拘留字符串对象地址取出1W次,存放在局部变量str中。这确实不需要什么时间。 
                   测试②中局部变量s1和s2存放的是两个不同的拘留字符串对象的地址。然后会通过下面三个步骤完成“+连接”:
                                    1、StringBuilder temp=new StringBuilder(s1),
                                    2、temp.append(s2);
                                    3、str=temp.toString();
                   我们发现,虽然在中间的时候也用到了append()方法,但是在开始和结束的时候分别创建了StringBuilder和String对象。可想而知:调用1W次,是不是就创建了1W次这两种对象呢?不划算。

         但是,String变量的"+连接"操作比String常量的"+连接"操作使用的更加广泛。 这一点是不言而喻的。
        

    (2)String对象的"累+"连接操作与StringBuffer对象的append()累和连接操作比较。 
              ▲测试①代码:     (代码位置1)  String s1="Heart";
                                                           String s="";
                                        (代码位置2)  s=s+s1;
                 [耗时:  4200—4500ms]
                 
              ▲测试②代码        (代码位置1)  String s1="Heart";
                                                           StringBuffer sb=new StringBuffer();
                                        (代码位置2) sb.append(s1);
                 [耗时:  0ms(当循环100000次的时候,耗时大概16—31ms)]
             结论:大量字符串累加时,StringBuffer的append()效率远好于String对象的"累+"连接 
             原因:测试① 中的s=s+s1,JVM会利用首先创建一个StringBuilder,并利用append方法完成s和s1所指向的字符串对象值的合并操作,接着调用StringBuilder的 toString()方法在堆中创建一个新的String对象,其值为刚才字符串的合并结果。而局部变量s指向了新创建的String对象。

                      因为String对象中的value[]是不能改变的,每一次合并后字符串值都需要创建一个新的String对象来存放。循环1W次自然需要创建1W个String对象和1W个StringBuilder对象,效率低就可想而知了。


                      测试②中sb.append(s1);只需要将自己的value[]数组不停的扩大来存放s1即可。循环过程中无需在堆中创建任何新的对象。效率高就不足为奇了。        

     

    ★ 镜头总结:

        (1) 在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高。

        (2) StringBuffer对象的append效率要高于String对象的"+"连接操作。

        (3) 不停的创建对象是程序低效的一个重要原因。那么相同的字符串值能否在堆中只创建一个String对象那。显然拘留字符串能够做到这一点,除了程序中的字符串常量会被JVM自动创建拘留字符串之外,调用String的intern()方法也能做到这一点。当调用intern()时,如果常量池中已经有了当前String的值,那么返回这个常量指向拘留对象的地址。如果没有,则将String值加入常量池中,并创建一个新的拘留字符串对象。

    全)

     

    参照:

    https://www.cnblogs.com/goody9807/p/6516374.html

    https://blog.csdn.net/u011702479/article/details/82262823

    https://blog.csdn.net/weixin_41101173/article/details/79677982

    展开全文
  • String、StringBuffer和StringBuilder的区别

    万次阅读 多人点赞 2019-06-16 10:31:19
    String、StringBuffer和StringBuilder的区别: 文章目录StringStringBufferStringBuilderStringBuffer是如何实现线程安全的呢?Java9的改进 String   String类是不可变类,即一旦一个String对象被创建以后,包含在...
  • 3、string stringbuffer stringbuilder的区别 String spring是一个不可变的类 他被final修饰 string重新赋值就是重新生成实例对象,如果原先的对象没有被引用,就会被垃圾回收 Stringbuffer stringbuffer 是可以变的...
  • StringBuilder详解

    万次阅读 2020-01-13 17:56:31
    还是一样,我们学习StringBuilder 之前先查一下一些定义,先做个简单的了解 首先就是创建Stringbuilder对象 StringBuilder sbuilder =new StringBuilder(); 我们来做个简单的对比:连接字符串 ...
  • StringBuilder

    2019-12-04 21:04:20
    StringBuilder类 1.1StringBuilder是什么 StringBuilder类来自于System.Text命名空间的一个对象,在使用的时候先得对StringBuilder进行初始化,在初始化的时候可以指定初始字符串以及初始长度。 1.2创建...
  • C# StringBuilder

    2019-10-22 17:45:58
    在Java中,我们可以使用stringbuffer来应对频繁更改字符串的问题,在C#中,同样也有StringBuilder,两者的功能十分类似。 StringBuilder的创建 StringBuilder sb = new StringBuilder("xxx");//使用构造器进行创建 ...
  • 关于StringBuilder

    2021-05-08 10:07:55
    String 和 StringBuilder 的区别 String StringBuilder 对象恒定不变 对象表示的字符串可变 所以StringBuilder可以克服String对象恒定带来的影响 StringBuilder有四个构造函数: StringBuilder(); value...
  • StringBuilder 拼接

    千次阅读 2019-04-09 16:53:10
    package com.company; public class demo { public static void main(String[] args) { // 创建一个StringBuilder对象,用来存储字符串 StringBuilder hobby = new StringBuilder ("爱慕课"); StringB...
  • StringBuilder简介

    2020-12-01 22:17:09
    StringBuilder 构造方法: public StringBuilder(): 构造一个不带任何字符的字符串生成器,其初始容量为16个字符 public StringBuilder(String str):构造一个字符串生成器,并初始化为指定的字符串内容 常用方法: ...
  • JAVA StringBuilder

    2020-11-17 17:38:44
    JAVA StringBuilder StringBuilder概述 如果对字符串进行拼接操作,每次拼接吗,都会归建一个新的 String 对象,既耗时,又浪费内存空间,而这种操作还不可避免。那么有没有一种比较好的方式可以解决这个问题?答案...
  • StringBuilder问题

    2015-08-11 17:25:49
    StringBuilder问题
  • StringBuilder 我们在使用字符串时会产生很多废弃的值,虽然C#中有垃圾回收机制,但是如果一个字符串所存的值非常大,这就非常占用空间。 我们该怎么解决这个问题呢? StringBuilder 使用StringBuilder追加后就不是...
  • StringBuilder原理

    千次阅读 2019-05-01 17:55:49
    1、StringBuilder和StringBuffer区别 其实StringBuilder和StringBuffer用法都是一样,几乎没啥区别,我比较常用StringBuilder。 但是说它们之间到底有没有区别,那肯定是有的。 StringBuffer的大多数方法都是用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 333,645
精华内容 133,458
关键字:

stringbuilder