在java程序中,字符串的处理,是非常常见的。java中,字符串处理的类有,String,StringBuffer,StringBuilder类。下面简单总结下java程序里的字符串各种处理的区别。
String literal(字面量字符串)
字面量字符串(String literal)就是那些直接在代码文件里用双引号扩起来的字符串申明,比如String str=”abc”。当字面量字符串被创建的时候,JVM就会把”abc”这个字符串对象放到字符串常量池里。如果下次再”abc”这个字符串的引用,则直接使用常量池里的对象,而不是重新创建新的String对象。
public class TestStringLiteral {
public static void main( String[] args ){
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);
//true
//字面量字符串不会被重复创建
}
}
String类
String类是不可变(immutable)的。在java中,所有不可变对象都是线程安全的。所以,字符串是不能被两个线程同时使用的。字符串对象一旦被分配,就永远不会再被改变。所有针对String类做改变的操作,都是创建了一个新的字符串。而之前对象的内容,是不会被改变。
public class TestString {
public static void main( String[] args ){
String str1 = new String("hello");
String str2 = str1;
System.out.println(str1 == str2);
//true
str2 = str2.replace('h', 'H');
//新建了一个新的String对象
System.out.println(str1);
//hello
System.out.println(str2);
//Hello
System.out.println(str1 == str2);
//false
}
}
intern函数
使用new创建的String对象,是不会放到字符串常量池中的。如果想把new出来的String对象放到字符串常量池中,可以使用String提供的intern函数。intern函数会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。
public class TestIntern {
public static void main( String[] args ){
String str1 = new String("hello");
String str2 = "hello";
String str3 = str1.intern();
System.out.println(str1 == str2);
//false
System.out.println(str2 == str3);
//true
//都指向常量池里的对象
}
}
字符串常量池的好处,就是减少相同字符串的创建,节约内存。但字符串常量池里的所有字符串都是不会被GC回收的。
字符串常量池的基础就是上面介绍的String类是不可变的。
String几个小问题
1.如下代码,创建了几个String对象
String s = new String("abc")
A:创建了两个对象。第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。正如上面TestIntern的例子代码里,str1和str2并没有指向同一个对象。
2.下面代码,创建了几个对象
String test = "a" + "b" + "c";
A:只创建了一个对象,在常量池中也只保存一个引用,即字符串abc。因为,JVM对初始化的连接操作字符串进行了优化。
StringBuilder
StringBuffer是可变的。StringBuffer被创建了之后,会在堆(heap)内存区new一块内存。StringBuffer的所有修改操作,都是直接改变heap区域内的值。同时,StringBuilder也是线程不安全的,如果有两个线程同时对StringBuilder对象进行修改,则会出问题。
public class TestStringBuilder {
public static void main( String[] args ){
StringBuilder strBuilder1 = new StringBuilder("hello");
StringBuilder strBuilder2 = strBuilder1;
System.out.println(strBuilder1 == strBuilder2);
//true
strBuilder1.append(" bye");
System.out.println(strBuilder1);
//hello bye
//因为StringBuilder对象直接改变内存里值
System.out.println(strBuilder2);
//hello bye
System.out.println(strBuilder1 == strBuilder2);
//true
}
}
StringBuffer
StringBuffer类和StringBuilder类一样,都是可变的,同时也拥有相同的函数接口。但与StringBuilder不同的是,StringBuffer是线程安全的。由于,每个操作都需要进行线程同步操作。所以,StringBuffer类会比StringBuilder类慢。除非有特殊需要,否则,最好是使用StringBuilder来代替StringBuffer。
字符串连接性能比较
public class StringTest {
final static String TEST_STRING = "abcdefghijklmnopsdsdfsad";
final static int LOOP_COUNT = 10000;
static String useString(){
String result = "";
for (int i=0; i<LOOP_COUNT; ++i){
result += TEST_STRING;
}
return result;
}
static String useStringBuilder(){
StringBuilder builder = new StringBuilder();
for (int i=0; i<LOOP_COUNT; ++i){
builder.append(TEST_STRING);
}
return builder.toString();
}
static String userStringBuffer(){
StringBuffer buffer = new StringBuffer();
for (int i=0; i<LOOP_COUNT; ++i){
buffer.append(TEST_STRING);
}
return buffer.toString();
}
public static void main( String[] args ) {
long begin = System.currentTimeMillis();
useString();
long strEnd = System.currentTimeMillis();
useStringBuilder();
long builderEnd = System.currentTimeMillis();
userStringBuffer();
long bufferEnd = System.currentTimeMillis();
System.out.println("String time:"+ (strEnd - begin));
System.out.println("StringBuilder time:" + (builderEnd - strEnd));
System.out.println("StringBuffer time:" + (bufferEnd - builderEnd));
}
}
上面程序,在我的机器上的输出结果为:
String time:2802
StringBuilder time:3
StringBuffer time:11
从上面的结果可以看到,String类比StringBuilder类的性能要差很多。所以,千万不能使用”+”进行String字符串的拼接操作,要优先使用StringBuilder的append操作。