一、String创建方式
1.构造方法
使用new关键字创建字符串对象。
String str = new String("123");
2.字面量
类似于直接赋值的方式。
String str = "123";
字面量与构造方法创建出来的字符串是有区别的,下面将介绍两者之间的区别。通过new创建出来的字符串会存储在堆里,并且有自己的空间。
通过字面量创建出来的字符串会被放到字符串常量池中,如果字符串常量池中没有相同内容的字符串,会先在字符串常量池中创建该字符串,然后将引用地址返回变量,如下图所示。
如果字符串常量池中有相同内容的字符串,则返回已经存在的字符串的引用给变量。
为了便于理解,举了下文的例子。从图中可以看出,s4和s5所指对象是不同的,而对象内容是相同的,所以第一个输出为false,第二个输出为true。
String s1 = "Runnoob"; //String直接创建
String s2 = "Runnoob"; //String直接创建
String s3 = s1; //相同引用
String s4 = new String("Runnoob"); //String对象创建
String s5 = new String("Runnoob"); //String对象创建
System.out.println(s4==s5); //false
System.out.println(s4.equals(s5)); //true
既然两种创建方式存储的位置不同,那我有没有什么方法能将堆中的String对象放到字符串常量池中呢?当然有,使用intern()方法,例子如下。
String str1 = "Stack to pool";
String str2 = new String("Stack to pool");
System.out.println(str1 == str2); //false
System.out.println(str1 == str2.intern()); //true
为什么要将堆中的String对象放到字符串常量池中呢?因为这样可以提升内存使用效率,同时让使用者共享唯一的实例。intern()方法的大致流程是:
1、先判断字符串常量池中是否含有相同(通过equals方法)的字符串字面量,如果有直接返回字符串字面量;
2、如果不含,则将该字符串对象添加到字符串常量池中,同时返回该对象在字符串常量池的引用。返回的引用需要赋值才可,否则还是会指向堆中的地址,即:
String str1 = "Stack to pool";
String str2 = new String("Stack to pool");
System.out.println(str1 == str2.intern()); //true
System.out.println(str1 == str2); //false
str2 = str2.intern();
System.out.println(str1 == str2); //true
二、String特点
1. 不可变性
String对象一旦在堆中创建出来,就无法再修改。那是因为String对象放在char数组中,该数组由final关键字修饰,不可变。注意:String不可变不是因为自身被final修饰,而是因为它底层存储是一个不可变的数组,程序无法对它进行修改。
/**
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
* implemented as instances of this class.
* <p>
* Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared. For example:
* ...
*/
public final class String {
private final char value[];
}
如果把新的值赋给已经定义的字符串,此时不可变性是如何体现的呢?可以看出是不改变原来的字符串Hello future,而是创建了新的字符串Hello。
String str1 = new String("Hello world");
String str2 = "Hello world";
String str3 = "Hello future";
str3 = "Hello";
2.安全性
因为是不可变对象,所以String是线程安全的。
三、String使用
1.拼接
通过String变量 + 字符串常量方式得到的结果会在堆中,不在常量池中,当然可以通过intern()方法放进常量池中,同时不仅"+"如此,调用substring(),toUpperCase(),trim()等返回的都是String在堆中的地址。
String str1 = "good good";
String str2 = str1 + "study";
String str3 = "good good study";
System.out.println(str2 == str3); //false
2.不可变性的不足
不可变性最大的不足是任何修改都会产生新的对象,而新的对象就会引起创建对象的开销,解决方法是使用StringBuffer或StringBuilder。首先看下面的一个例子。
@Test
public void testPerformance(){
String str0 = "hello,world";
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str0 += i;
}
System.out.println(System.currentTimeMillis() - start); //38833
StringBuilder sb = new StringBuilder("hello,world");
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
sb.append(i);
}
System.out.println(System.currentTimeMillis() - start1); //4
StringBuffer sbf = new StringBuffer("hello,world");
long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
sbf.append(i);
}
System.out.println(System.currentTimeMillis() - start2); //4
}
可以看出执行时间差别很大,为了解决String不擅长的大量字符串拼接这种业务场景,所以我们引入了StringBuffer和StringBuilder。
StringBuffer与StringBuilder的允许时间基本一致,那为什么需要定义两个功能相似的类呢?
@Test
public void testSafe() throws InterruptedException {
String str0 = "hello,world";
StringBuilder sb = new StringBuilder(str0);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb.append("a");
}).start();
}
StringBuffer sbf = new StringBuffer(str0);
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
sbf.append("a");
}).start();
}
// 等待工作线程运行结束
while (Thread.activeCount()>2){
}
System.out.println("StringBuilder:"+sb.toString().length()); //109
System.out.println("StringBuffer:"+sbf.toString().length()); //111
}
本来拼接完程度应该是111的,但是现在呢,StringBuilder是109,说明了StringBuilder不是安全的,也就是说在多线程的环境下,我们应该使用StringBuffer。
下面我们对比了String、StringBuffer与StringBuilder的区别
String | StringBuffer | StringBuilder |
---|---|---|
final修饰,不可继承 | final修饰,不可继承 | final修饰,不可继承 |
字符串常量,创建后不可变 | 字符串变量,可动态修改 | 字符串变量,可动态修改 |
不存在线程安全问题 | 线程安全,所有public方法由synchronized修改 | 先从不安全 |
大量字符串拼接效率最低 | 大量字符串拼接效率非常高 | 大量字符串拼接效率最高 |
参考资料:
1.Java数据类型—String基础
2.Java数据类型—StringBuilder与StringBuffer
3.Java String 类