JavaString、StringBuilder以及StringBuffer
一、JavaString类
关于String类的两种创建方法:
public class test1 {
public static void main(String[] args) {
String s1 = "Runoob"; // String 直接创建
String s2 = "Runoob"; // String 直接创建
String s3 = s1; // 相同引用
String s4 = new String("Runoob");// String 对象创建
String s5 = new String("Runoob");// String 对象创建
System.out.println(s1==s4);//false
System.out.println(s1==s2);//true
System.out.println(s2==s3);//true
System.out.println(s4==s5);//false
}
}
原因:
- String 创建的字符串存储在公共池中,而new创建的字符串对象在堆上。
- 由Strin创建的字符串都在编译期间生成了字面常量和符号引用,运行期间字面常量“Ruboob”被存储在公共池(当然只是保存了一份)。这种方式来将String对象跟引用绑定的话,JVM执行引擎会在运行公共池查找是否存在相同的字面常量,如果存在,则直接将引用指向已经存在的字面常量,并将引用指向该字面常量。
- 众所周知,通过nwq关键字来生成对象是子堆区进行的,而在堆区进行对象的生成的过程是不会去检测对象是否存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容分是相同的。
我们通过String源码来了解该类的实现。
打开这个类文件,我们就可以发现String类是被final修饰的。
public final class String implements Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
static final boolean COMPACT_STRINGS = true;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
从上面可以看出几点:
- String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final。在java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中,被final修饰的方法会被转为内嵌调用以提升执行效率。而从JavaSE5/6开始,就渐渐摒弃这种方式了,因此在现在的JavaSE版本中,不需要考虑final去提升方法调用效率。只有在确定不想让该方法被覆盖的时,才将方法设置为final。
- 上面列举出了String类中所有的成员属性,从上面可以看出String类其实是通过char数组来保存字符串的。
public String substring( int beginIndex, int endIndex) {
if (beginIndex < 0 ) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0 ) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0 ) {
return this ;
}
char buf[] = new char [count + otherLen];
getChars( 0 , count, buf, 0 );
str.getChars( 0 , otherLen, buf, count);
return new String( 0 , count + otherLen, buf);
}
public String replace( char oldChar, char newChar) {
if (oldChar != newChar) {
int len = count;
int i = - 1 ;
char [] val = value; /* avoid getfield opcode */
int off = offset; /* avoid getfield opcode */
while (++i < len) {
if (val[off + i] == oldChar) {
break ;
}
}
if (i < len) {
char buf[] = new char [len];
for ( int j = 0 ; j < i ; j++) {
buf[j] = val[off+j];
}
while (i < len) {
char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String( 0 , len, buf);
}
}
return this ;
从上面的三个方法可以看出,无论是sub操作,concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。
String a="a"; //假设a的地址是0x00001;
a="b"; //重新赋值以后a的会指向b的地址0x00002,但是0x00001地址依旧存在。
因此String的操作都是改变赋值地址而不是改变值操作。
二、JavaStringBuffer和StringBuilder类
-
当对字符串进行修改的时候,需要使用StringBuffer和StringBulider类。和String类不同是,StringBufffer和StringBuilder类的对象能够被多次的修改,并且不产生新的未使用对象。
-
在使用StringBuffer类时,每次都会对StringBuffer对象本身进行操作,而股市生成新的对象,所以如果需要对字符串进行修改使用StringBuffer。
-
那么有人就会问了,既然由了StringBuilder类,为什么还需要StringBuffer类?事实上,S听Builder和StringBuilder类拥有的成员属性以及成员方法基本相同,区别是StringBuffer类的成员方法前面多了一个关键字:synchronized,不用多说,这个关键字是在多线程访问时起到安全保护的作用的,也就是说StringBuffer是线程安全的。
下面2段代码分别是StringBuffer和StringBuilder,insert方法的具体实现。
StringBuilder的insert方法
public StringBuilder insert(int index, char str[], int offset,
int len)
{
super.insert(index, str, offset, len);
return this;
}
StringBuilder的insert方法
public synchronized StringBuffer insert(int index, char str[], int offset,
int len)
{
super.insert(index, str, offset, len);
return this;
}
三、不同场景下三个类的性能测试
下面我们来测试以下三个类的性能区别,以及字符串直接连接和间接连接的区别。
public class test2 {
private static int time=50000;
public static void main(String[] args) {
testString();
testStringBuffer();
testStringBuilder();
test1String();
test2String();
}
public static void testString(){
String s="";
long begin=System.currentTimeMillis();
for (int i=0;i<time;i++){
s+="java";
}
long over=System.currentTimeMillis();
System.out.println("操作"+s.getClass().getName()+"类型使用的时间是:"+(over-begin)+"毫秒");
}
public static void testStringBuffer(){
StringBuffer sb=new StringBuffer();
long begin=System.currentTimeMillis();
for (int i=0;i<time;i++ ){
sb.append("java");
}
long over=System.currentTimeMillis();
System.out.println("操作:"+ sb.getClass().getName()+"类型使用的时间是:"+(over-begin)+"毫秒");
}
public static void testStringBuilder(){
StringBuilder sb=new StringBuilder();
long begin=System.currentTimeMillis();
for (int i=0;i<time;i++){
sb.append("java");
}
long over=System.currentTimeMillis();
System.out.println("操作"+sb.getClass().getName()+"类型使用的时间是:"+(over-begin)+"毫秒");
}
public static void test1String(){
long begin=System.currentTimeMillis();
for (int i=0;i<time;i++){
String s="I"+"love"+"you";
}
long over=System.currentTimeMillis();
System.out.println("字符串之间相加操作:"+(over-begin)+"毫秒");
}
public static void test2String(){
long begin=System.currentTimeMillis();
String s1="I";
String s2="love";
String s3="you";
for (int i=0;i<time;i++){
String s=s1+s2+s3;
}
long over=System.currentTimeMillis();
System.out.println("字符串间接相加操作:"+(over-begin));
}
}
事实上String+="java"的操作会自动被JVM优化,看下面这段代码:
public class Main {
private static int time = 50000;
public static void main(String[] args) {
testString();
testOptimalString();
}
public static void testString () {
String s="";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
s += "java";
}
long over = System.currentTimeMillis();
System.out.println("操作"+s.getClass().getName()+"类型使用的时间为:"+(over-begin)+"毫秒");
}
public static void testOptimalString () {
String s="";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++){
StringBuilder sb = new StringBuilder(s);
sb.append("java");
s=sb.toString();
}
long over = System.currentTimeMillis();
System.out.println("模拟JVM优化操作的时间为:"+(over-begin)+"毫秒");
}
}
从上面的执行结果进行一般性的解释:
-
对于直接相加字符串,效率很高,因为在编译器编译期间便确定了它的值,也就是说形如“I”+“love”+"you"的字符串相加,在编译期间便被优化成为了“Iloveyou”。
对于间接相加(即包含字符串引用),形如s1+s2+s3;效率要比直接相加低,因为在编译器不会对引用变量进行优化。 -
String、StringBuilder、StringBuffer三者的执行效率
StringBuilder>StringBuffer>String
当然这个是在多次修改值的时候的情况下成立。
比如像上面的那个例子。String s+="java"的效率就比StringBuilder s=new StringBuilder().append(“java”)要高。 -
因此根据不同的情况来进行选择使用:
当字符串相加操作或者改动较少的情况下,建议使用String str="hello"这种形式。
当字符串相加操作或者改动较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。