1 String 基础

  想要了解一个类,最好的办法就是看这个类的源代码,String类源代码如下:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; /** Cache the hash code for the string */
private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L; /**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];

  从上面代码可以看出:

  ① String类是final类,即意味着String类不能被继承,并且它的成员方法都默认为final方法。

  ② 上面列出了String类的成员属性,String类其实是通过char数组来保存字符串的。

  再来看String类的一些方法:

 public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
} public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
 public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */ while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}

  上面三个方法,无论是substring、concat、replace操作都不是在原来的基础上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串没有改变。

  牢记: 对String对象的任何改变都不会影响到原对象,相关的任何change操作都会产生新的对象

  2 深入理解String、StringBuffer、StringBuilder

  (1) String str = "Hello" 与 String str = new String("Hello") 的区别

 String str1="Hello latiny";
String str2="Hello latiny";
String str3=new String("Hello latiny");
String str4=new String("Hello latiny"); System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str3==str4);

  结果为:

true
false
false

  

  为什么会出现这样的结果?这里我们作一个详细解释

  ① 首先得引入常量池的概念,常量池指的是在编译期就被确定,并保存在已编译的.class文件中的一些数据。它包含了类、方法、接口等的常量,也包含了字符串常量。

String str1="Hello latiny"

String str2="Hello latiny";

其中 Hello latiny 都是字符串常量。它们在编译时就被确定了,所以str1 == str2为true

② 用new String()创建的字符串不是常量,不能在编译时确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。更进一步解释一下,通过new 关键字创建的对象是在堆区进行的,而在堆区进行对象的生成过程是不会去检测该对象是否已经存在。因此通过new创建的对象,一定是不同的对象,即使字符串内容相同。

(2) 既然在Java中已经存在了String类,为什么还需要StringBuffer与StringBuilder类呢?

看下面这段代码:

 public class StringTest {

     public static void main(String[] args) {

         String str = "";
for(int i=0; i<10000; i++)
{
str+="Hello";
} } }

  

 str+="Hello"; 这句代码的过程相当于将原有的 str 变量指向的对象内容取出与 Hello 作字符串相加操作,再存进另一个新的String 对象中,再让str变量的指向新生成的对象。反编译其字节码文件就知道了:
C:\Work\Project\Java\Eclipse\JustTest\bin\com\latiny\string>javap -c StringTest
警告: 二进制文件StringTest包含com.latiny.string.StringTest
Compiled from "StringTest.java"
public class com.latiny.string.StringTest {
public com.latiny.string.StringTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #16 // String
2: astore_1
3: iconst_0
4: istore_2
5: goto 31
8: new #18 // class java/lang/StringBuilder
11: dup
12: aload_1
13: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
16: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
19: ldc #29 // String Hello
21: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore_1
28: iinc 2, 1
31: iload_2
32: sipush 10000
35: if_icmplt 8
38: return
}

从这段反编译的字节码文件可以看出:从第8行到35行是整个循环执行过程, 并且每次循环都会new 出一个StringBuilder对象,然后进行append操作,最后通过toString 方法返回String对象。也就是说这个循环执行完毕new出了10000个对象, 这是非常大的资源浪费。从上面还可以看出:str+="Hello";  自动会被JVM优化成:

StringBuilder str1 = new StringBuilder(str);

str1.append("Hello");

str = str1.toString();

再来看下面这段代码:

public class StringTest {

	public static void main(String[] args) {

		StringBuilder str = new StringBuilder();
for(int i=0; i<10000; i++)
{
str.append("Hello");
} } }

  

反编译字节码文件得到如下代码:

C:\Work\Project\Java\Eclipse\JustTest\bin\com\latiny\string>javap -c StringTest
警告: 二进制文件StringTest包含com.latiny.string.StringTest
Compiled from "StringTest.java"
public class com.latiny.string.StringTest {
public com.latiny.string.StringTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: new #16 // class java/lang/StringBuilder
3: dup
4: invokespecial #18 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: goto 23
13: aload_1
14: ldc #19 // String Hello
16: invokevirtual #21 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: pop
20: iinc 2, 1
23: iload_2
24: sipush 10000
27: if_icmplt 13
30: return
}

  

上面代码可以看出,循环从13行到27行结束,并且new 操作只进行了一次,也就是说只产生了一个对象, append操作是在原有对象的基础上进行的。因此循环10000次之后,这段代码所占的资源比直接使用String定义的变量要小得多

那有人又要问了既然有了StringBuilder类,为什么还需要StringBuffer类,查看源代码就知道了,StringBuilder与StringBuffer类拥有的成员属性及方法基本相同,区别是StringBuffer类的成员方法多了一个关键字: synchronized,很明显这个关键字是在多线程访问时起到安全保护作用的,即StringBuffer是线程安全的。

StringBuilder的insert方法:

 @Override
public StringBuilder insert(int offset, Object obj) {
super.insert(offset, obj);
return this;
}

StringBuffer的insert方法:

     @Override
public synchronized StringBuffer insert(int index, char[] str, int offset,
int len)
{
toStringCache = null;
super.insert(index, str, offset, len);
return this;
}

  3 三个类不同场景的性能测试

public class StringTest {

    private static final int TIMES = 50000;

    public static void main(String[] args) {
TestString1();
TestBuilder();
TestBuffer(); // 直接字符相加与间接字符相加对比
TestString2();
TestString3();
} public static void TestString1()
{
String str = "";
long begin = System.currentTimeMillis();
for(int i=0; i<TIMES; i++)
{
str += "Java";
}
long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒");
} public static void TestBuilder()
{
StringBuilder str = new StringBuilder();
long begin = System.currentTimeMillis();
for(int i=0; i<TIMES; i++)
{
str.append("Java");
}
long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒");
} public static void TestBuffer()
{
StringBuffer str = new StringBuffer();
long begin = System.currentTimeMillis();
for(int i=0; i<TIMES; i++)
{
str.append("Java");
}
long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒");
} public static void TestString2()
{
String str = "";
long begin = System.currentTimeMillis();
for(int i=0; i<TIMES; i++)
{
str="I"+"Love"+"Java";
}
long end = System.currentTimeMillis(); System.out.println("字符串直接相加操作需要的时间:"+(end-begin)+"毫秒");
} public static void TestString3()
{
String str1 = "I";
String str2 = "Love";
String str3 = "Java";
String str = "";
long begin = System.currentTimeMillis();
for(int i=0; i<TIMES; i++)
{
str = str1+str2+str3;
}
long end = System.currentTimeMillis(); System.out.println("字符串间接相加操作需要的时间:"+(end-begin)+"毫秒");
} }

测试环境:win7 + Eclipse + JDK1.8

  结果为:

操作java.lang.String类型需要的时间:5395毫秒
操作java.lang.StringBuilder类型需要的时间:1毫秒
操作java.lang.StringBuffer类型需要的时间:2毫秒

字符串直接相加操作需要的时间:1毫秒
字符串间接相加操作需要的时间:4毫秒
请按任意键继续. . .

  上面提到JVM会自动优化 str+="Hello",看如下代码:

public static void  TestString1()
{
String str = "";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++)
{
str+="Java";
}
long end = System.currentTimeMillis(); System.out.println("操作"+str.getClass().getName()+"类型需要的时间:"+(end-begin)+"毫秒");
} public static void TestString1Optimal()
{
String str = "";
long begin = System.currentTimeMillis();
for(int i=0; i<time; i++)
{
StringBuilder str1 = new StringBuilder(str); str1.append("Java"); str = str1.toString();
}
long end = System.currentTimeMillis(); System.out.println("模拟JVM优化操作需要的时间:"+(end-begin)+"毫秒");
}

  执行结果:

操作java.lang.String类型需要的时间:11692毫秒
模拟JVM优化操作需要的时间:9958毫秒

  得到验证。

  对执行结果进行一般的解释:

① 对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如 "I"+"like"+"Java" 的字符串相加,在编译时被优化成 "Ilikejava"。对于间接相加(即包含字符串引用),刑如:str=str1+str2+str3 ,效率比直接相加低,因为在编译时编译器不会对引用变量进行优化。

② 三者的效率:

StringBuilder > StringBuffer > String

但是这只是相对的,如String str = "Hello" + "Latiny"; 比 StringBuilder str = new StringBuilder().append("Hello").append("Latiny"); 要高。

这三个类各有利弊,根据不同的情况选择使用:

当字符串相加操作或者改动较少时,使用String类;

当字符串相加操作较多时,使用StringBuilder吗,如果采用多线程,需要考虑线程安全则使用StringBuffer;

4 常见的String、 StringBuilder、StringBuffer面试题

(1) String a = "Hello2"; final String b = "Hello"; String c = b+"2"; System.out.println(a==c); 输出结果为true

(2) 下面代码输出结果为false:

     public static void main(String[] args) {
// TODO Auto-generated method stub
//TestString1();
//TestString1Optimal();
String a = "Hello2";
final String b = getHello();
String c = b+"2"; System.out.println(a==c);
} public static String getHello()
{
return "Hello";
}

  (3) 下面代码输出为true:

 public static void main(String[] args) {
// TODO Auto-generated method stub
//TestString1();
//TestString1Optimal();
String a = "Hello2";
String b = a.intern();
System.out.println(a==b);
}

(4) 代码 I 与 II 的区别

     String str = "I";
str += "like" + "Java"; //I
//str = str+"like" + "Java"; //II

  ① I的效率比II 高,I的 "like" + "Java" 在编译时会被优化为 likejava 而II 的不会

  I 的反编译字节码

public class com.latiny.string.StringTest1 {
public com.latiny.string.StringTest1();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #16 // String I
2: astore_1
3: new #18 // class java/lang/StringBuilder
6: dup
7: aload_1
8: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
11: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
14: ldc #29 // String likeJava
16: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: return
}

  II 的反编译字节码

  

public class com.latiny.string.StringTest1 {
public com.latiny.string.StringTest1();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: ldc #16 // String I
2: astore_1
3: new #18 // class java/lang/StringBuilder
6: dup
7: aload_1
8: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
11: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
14: ldc #29 // String like
16: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #35 // String Java
21: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #37 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore_1
28: return
}

  可以看出 I 中只进行了一次append操作,II 的进行了两次。

转自:http://www.cnblogs.com/dolphin0520/p/3778589.html

Java 中的String、StringBuilder与StringBuffer的区别联系(转载)的更多相关文章

  1. Java基础学习总结(65)——Java中的String,StringBuilder和StringBuffer比较

    字符串,就是一系列字符的集合. Java里面提供了String,StringBuffer和StringBuilder三个类来封装字符串,其中StringBuilder类是到jdk 1.5才新增的.字符 ...

  2. String,StringBuilder与StringBuffer的区别

    相信大家看到过很多比较String和StringBuffer区别的文章,也明白这两者的区别,然而自从Java 5.0发布以后,我们的比较列表上将多出一个对象了,这就是StringBuilder类.St ...

  3. String, StringBuilder 与StringBuffer的区别与联系

    1.区别 (1)String构建的对象不能改变,每次对String进行操作时,如两个String相加,需要新建一个String对象,然后容纳最终的结果. 而StringBuilder与StringBu ...

  4. Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  5. Java中的String,StringBuilder,StringBuffer三者的区别(转载)

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  6. 转:Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  7. [转载]Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

  8. Java中的String,StringBuilder,StringBuffer的区别

    这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面. 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > ...

  9. 【转】Java中的String,StringBuilder,StringBuffer三者的区别

    https://www.cnblogs.com/su-feng/p/6659064.html 最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及String ...

随机推荐

  1. 《Java大学教程》—第6章 类和对象

    6.2 对象:结构化编程-->数据-->封装(聚合,信息隐藏)-->对象(方法及其操作的数据都聚合在一个单元中,作为更高层的组织单元)-->类(创建对象的模板)6.3 类:*  ...

  2. ZooKeeper Observers解决节点过多时写性能下降问题

    ZooKeeper Observers Observers: Scaling ZooKeeper Without Hurting Write Performance How to use Observ ...

  3. nginx入门与实战

    网站服务 想必我们大多数人都是通过访问网站而开始接触互联网的吧.我们平时访问的网站服务 就是 Web 网络服务,一般是指允许用户通过浏览器访问到互联网中各种资源的服务. Web 网络服务是一种被动访问 ...

  4. 设计模式のStatePattern(状态模式)----行为模式

    一.产生背景 在面向对象软件设计时,常常碰到某一个对象由于状态的不同而有不同的行为.如果用if else或是switch case等方法处理,对象操作及对象的状态就耦合在一起,碰到复杂的情况就会造成代 ...

  5. docker实战练习(一)

    systemctl start docker systemctl pause docker systemctl unpause docker systemctl start docker system ...

  6. idea在maven打包时运行Test测试, 导致打包失败, 乱七八糟的错误

    在maven打包时运行Test测试, 导致打包失败, 乱七八糟的错误 在maven projects中图标toggle'skip Tests' Mode //宏杰帮助 网上案例:https://blo ...

  7. 安利一个_Java学习笔记总结

    javaIO 字符编码 多线程 线程池 ArrayList遍历方式 LinkedList遍历方式 Vector遍历方式 Vector, ArrayList, LinkedList 的区别是什么? Ha ...

  8. python入门学习:5.字典

    python入门学习:5.字典 关键点:字典 5.1 使用字典5.2 遍历字典5.3 嵌套 5.1 使用字典   在python中字典是一系列键-值对.每个键都和一个值关联,你可以使用键来访问与之相关 ...

  9. Android 6.0以后的版本报错:open failed: EACCES (Permission denied)

    Android 6.0以后的版本报错:open failed: EACCES (Permission denied) 在开发项目中,遇见要进行文件操作,遇见Caused by: android.sys ...

  10. centos7修改系统语言为简体中文

    centos7修改系统语言为简体中文 说明 自己装系统时一般都可以自定义选择系统语言.可是云端服务器一般都是安装好的镜像,默认系统语言为英文,对于初学者可能还会有搞不懂的计算机词汇.这里简单说一下ce ...