原理性及JVM中字符串的存储方式可参考文章:https://www.cnblogs.com/goody9807/p/6516374.html (非常清晰,从JVM内存角度进行了解释)
一、String
在编译期中,即形成 class文件的时候,双引号定义的字符串常量就会在class文件中被解释成常量表。当被类加载器加载到JVM时,会给class对象开始分配内存,常量表会被放到方法区中,同时会在堆中的永生代创建常量表各个字符串对应的拘留(永驻)对象,该对象的值是常量表的字符串。
String 值是不可变的,它是一个常量。当创建一个字符串时,会先判断常量池(堆中存储拘留对象)中是否有这个字符串,若没有,则创建拘留对象(存储常量值),同时在堆中创建该对象,该对象存储拘留对象中的值。
PS:我们可以把上述说的拘留对象认为是在字符串常量池。
字符串常量池是在堆(分为新生代、老年代、永生代)上的永生代(长驻留区)中。
示例:
1、String s = new String(“xyz”); //创建了几个对象
创建了一个或两个,若常量池中有了 “xyz”,则在堆中创建一个对象,其value值为xyz,此时就创建了一个对象;若常量池中没有 xyz,则需创建,同时堆中也创建,此时就是2个对象。
这个是在运行期间才创建的。
2、String s = "a"+"b"+"c"+"d"; //创建了几个对象
创建了1个或7个,和JVM本身有关。
JVM会将这种当成 StringBuffer来处理,在它眼里就是 String s = "abcd";
这个是在编译期中就已经创建好。
3、String s = "abc"; //创建了几个对象
创建了0个或1个,字符串常量池中若没有 abc会创建,然后 s 直接指向 abc在常量池中的内存地址。若有的话,直接指向常量池中的内存地址。不会在堆上再给 s 分配内存块空间,这也是与new String 不一样的地方。
4、String a = "a";
String b = "b";
String c = a+b+"c"; //创建了几个对象
在类加载分配内存时,字符串常量池中创建了 “a” "b",且 a、b 指向了字符串常量池对应的内存地址。由于c是引用变量,在运行期中,在字符串常量池中创建了 "c",接着使用 StringBuffer 来进行拼接。
相当于:
String c = new StringBuffer(a).append(b).append(“c”).toString();
建议:
1) 在平时构建String 时,第三种方式效率高于 第一种;
2) 字符串在运行过程中经常改变的话,不建议使用 string 对象,如果用String保存一个经常被修改的字符串,该字符串每次修改时都会创建新的无用的对象,这些无用的对象会被垃圾回收器回收,会影响程序的性能,不建议这么做。
二、StringBuffer
它是线程安全的可变字符序列,一个类似String的字符缓冲区。可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
三、StringBuilder
用法和 StringBuffer差不多,区别在于其非线程安全。
四、三者的区别
1、String 和 StringBuffer
String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
但是在一的 3 示例例外,速度和 StringBuffer一样。
在 一 的4示例中,String c = a+b 使用方式和 StringBuffer类似,但是从4个整个例子来看,产生了多个无用的字符串常量对象。总的来说无 StringBuffer好用;
2、StringBuffer 和 StringBuilder
StringBuffer是线程安全的
StringBuilder(5.0版本后添加的类,是StringBuffer的一个简单替换)为非线程安全的,但是效率会好些,在单线程环境中要做大量字符串累加时推荐使用该类
Java提供了String、StringBuffer和StringBuilder类来封装字符串,并提供了一系列操作字符串对象的方法。
它们的相同点是都用来封装字符串;都实现了CharSequence接口。它们之间的区别如下:(以下来源:https://cloud.tencent.com/developer/article/1414756)
一、可变与不可变
String类是一个不可变类,即创建String对象后,该对象中的字符串是不可改变的,直到这个对象被销毁。StringBuffer与StringBuilder都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,是可变类。
由于String是可变类,适合在需要被共享的场合中使用,当一个字符串经常被修改时,最好使用StringBuffer实现。如果用String保存一个经常被修改的字符串,该字符串每次修改时都会创建新的无用的对象,这些无用的对象会被垃圾回收器回收,会影响程序的性能,不建议这么做。
二、初始化方式
当创建String对象时,可以利用构造方法String str = new String("Java")的方式来对其进行初始化,也可以直接用赋值的方式String s = "Java"来初始化。而StringBuffer只能使用构造方法StringBuffer sb = new StringBuffer("hello")的方式初始化。
三、字符串修改方式
String字符串修改方法是首先创建一个StringBuffer,其次调用StringBuffer的append方法,最后调用StringBuffer的toString()方法把结果返回,示例代码如下:
String str = "hello";
str += "java";
以上代码等价于下面的代码:
StringBuffer sb = new StringBuffer(str);
sb.append("java");
str = sb.toString();
上述String字符串的修改过程要比StringBuffer多一些额外操作,会增加一些临时的对象,从而导致程序的执行效率降低。StringBuffer和StringBuilder在修改字符串方面比String的性能要高。
四、是否实现了equals和hashCode方法
String实现了equals()方法和hashCode()方法,new String("java").equals(new String("java"))的结果为true;
而StringBuffer没有实现equals()方法和hashCode()方法,因此,new StringBuffer("java").equals(new StringBuffer("java"))的结果为false,将StringBuffer对象存储进Java集合类中会出现问题。
五、是否线程安全
StringBuffer与StringBuilder都提供了一系列插入、追加、改变字符串里的字符序列的方法,它们的用法基本相同,只是StringBuilder是线程不安全的,StringBuffer是线程安全的,。如果只是在单线程中使用字符串缓冲区,则StringBuilder的效率会高些,但是当多线程访问时,最好使用StringBuffer。
综上,在执行效率方面,StringBuilder最高,StringBuffer次之,String最低,对于这种情况,一般而言,如果要操作的数量比较小,应优先使用String类;如果是在单线程下操作大量数据,应优先使用StringBuilder类;如果是在多线程下操作大量数据,应优先使用StringBuilder类。