一、java中String、StringBuffer和StringBuilder的区别
1.1 简单介绍
java中用于处理字符串常用的有三个类:
1、java.lang.String
2、java.lang.StringBuffer
3、java.lang.StrungBuilder
三者共同之处:都是final类,不允许被继承,主要是从性能和安全性上考虑的,因为这几个类都是经常被使用着,且考虑到防止其中的参数被参数修改影响到其他的应用。
StringBuffer是线程安全,可以不需要额外的同步用于多线程中;
StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了;
StringBuffer与StringBuilder两者共同之处:可以通过append、insert、deleteCharAt等进行字符串的操作。用法上一样。
String实现了三个接口:Serializable、Comparable、CarSequence
StringBuilder只实现了两个接口Serializable、CharSequence,相比之下String的实例可以通过compareTo方法进行比较,其他两个不可以。
String是一个字符串常量,属于引用类型,字符串一旦创建就不可改变,对于String字符串类型一旦发生改变,就会返回新的字符串对象。
在这里插入图片描述从源码中我们可以看出,String字符串底层fina修饰的char[]数组,一个char字符在内存中占两个字节(Unicode编码)value[]:用于储存String的内容。
我们来看一个例子:
String s = "Hello World";
创建字符串常量时,JVM会首先检查在字符串常量池,如果在字符串常量池中存在了该字符串 “Hello World”,此时就会将该字符串对象的地址值赋值给引用 s(s存放在栈中) .如果字符串不在常量池中,就会在常量池中创建字符串,然后将字符串对象的地址值交给s.值得注意的是:这里只创建了1个或0个字符串对象(Hello World已存在常量池就不会创建了)。
再看下一个例子:
String s = new String("Hello Java");
创建字符串常量时,JVM会首先检查字符串常量池,如果字符串"Hello Java"已经存在常量池中,直接复制堆中这个对象的副本,然后将堆中的地址值赋给引用s,不会在字符串常量池中创建对象.如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,并将对象的地址值交给s。 值得注意的是:这里会创建两个字符串对象,分别在常量池中与堆中(不考虑对象已存在的情况)。
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。
1、首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:
String str="abc";
System.out.println(str);
str=str+"de";
System.out.println(str);
运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。
另外,有时候我们会这样对字符串进行赋值:
String str="abc"+"de";
StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
System.out.println(str);
System.out.println(stringBuilder.toString());
stringBuilder.deleteCharAt(stringBuilder.length()-1);//删除指定位置的字符
这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和String str="abcde";是完全一样的,所以会很快,而如果写成下面这种形式
String str1="abc";
String str2="de";
String str=str1+str2;
那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
public static void main(String[] args) {
long a=new Date().getTime();
String cc="";
int n=10000;
for (int i = 0; i < n; i++) {
cc+="."+i;
}
System.out.println("String使用的时间"+(System.currentTimeMillis()-a)/1000.0+"s");
long s1=System.currentTimeMillis();
StringBuilder sb=new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append("."+i);
}
System.out.println("StringBuilder使用的时间"+(System.currentTimeMillis()-s1)/1000.0+"s");
long s2=System.currentTimeMillis();
StringBuffer sbf=new StringBuffer();
for (int i = 0; i < n; i++) {
sbf.append("."+i);
}
System.out.println("StringBuffer使用的时间"+(System.currentTimeMillis()-s2)/1000.0+"s");
}
1.2 再来说线程安全
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的。
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
(一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞)
1.3 总结一下
String:适用于少量的字符串操作的情况。
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况。
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
二、字符串常量池介绍
2.1 简单介绍
Java中的字符串常量池(String Pool)是存储在Java堆内存中的字符串池。我们知道String是java中比较特殊的类,我们可以使用new运算符创建String对象,也可以用双引号(”“)创建字串对象。
2.2 Java中的字符串常量池
下图,清楚地解释了如何在Java堆内存中维护字符串常量池,以及当我们使用不同的方式创建字符串时在堆内存中如何存放。
之所以有字符串常量池,是因为String在Java中是不可变(immutable)的,它是String interning概念的实现。字符串常量池也是亨元模式(Flyweight)的实例。
字符串常量池有助于为Java运行时节省大量空间,虽然创建字符串时需要更多的时间。
当我们使用双引号创建一个字符串时,首先在字符串常量池中查找是否有相同值的字符串,如果发现则返回其引用,否则它会在池中创建一个新的字符串,然后返回新字符串的引用。
如果使用new运算符创建字符串,则会强制String类在堆空间中创建一个新的String对象。我们可以使用intern()方法将其放入字符串常量池或从字符串常量池中查找具有相同的值字符串对象并返回其引用
如下是上文图中有关字符串常量池的程序实现:
package cn.gavin.basic;
public class StringTest {
public static void main(String[] args) {
// String s0 = "hello";
// String s1 = "hello";
// String s2 = "he" + "llo";
// System.out.println( s0 == s1 ); // true
// System.out.println( s0 == s2 ); // true
// System.out.println( s0.equals(s1) ); // true
System.out.println("-----------------------");
String s1 = "Cat";
String s2 = "Cat";
String s3 = new String("Cat"); // 创建1或2个对象,至少创建1个对象/这里只创建1个,因为常量池有了
String s4 = new String("Cat");// 创建1个
String s5= "Ca";
String s6= s5+"t";
String s7= "Ca"+"t";
System.out.println("s1 == s2 :"+(s1==s2));
System.out.println("s1 == s3 :"+(s1==s3));
System.out.println("s3 == s4 :"+(s3==s4));
System.out.println("s1 == s6 :"+(s1==s6));
System.out.println("s1 == s7 :"+(s1==s7));
System.out.println("-----------------------");
// String 有重写equals 比较的是对象,不是地址
System.out.println("equals s1-s2 :"+s1.equals(s2));
System.out.println("equals s1-s3 :"+s1.equals(s3));
System.out.println("equals s3-s4 :"+s3.equals(s4));
System.out.println("equals s1-s6 :"+s1.equals(s6));
System.out.println("equals s1-s7 :"+s1.equals(s7));
}
}
上述程序的输出是
s1 == s2 :true
s1 == s3 :false
s3 == s4 :false
s1 == s6 :false
s1 == s7 :true
equals s1-s2 :true
equals s1-s3 :true
equals s3-s4 :true
equals s1-s6 :true
equals s1-s7 :true
有时在java面试中,你会被问到有关字符串常量池的问题。例如,在下面的语句中创建了多少个字符串对象:
String str = new String("Cat");
在上面的语句中,可能创建1或2个字符串对象。如果池中已经有一个字符串“Cat”,那么池中只会创建一个字符串“str”。如果池中没有字符串字面量“Cat”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共2个字符串对象。
2.3 字符串常量池简单了解
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为 了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中, 就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突 进行共享。
2.4 Java字符串常量池是什么?为什么要有这种常量池?
1、存储在Java堆内存中的字符串池
2、为了让数据不冲突进行共享等
有人会问一个问题:
String A = "ABC";
String B = new String("ABC");
这两者有啥区别?
直接赋值的说法是字符串直接量
当程序第一次使用某个字符串直接量时,Java会使用常量池(constant pool) 来缓存该字符串直接量
如果程序后面再次用到该字符串直接量时,Java会直接使用常量池中存在的字符串直接量
比较方法:
==:比较引用类型比较的是地址值是否相同
equals:比较引用类型默认也是比较地址值是否相同,注意:String类重写了equals()方法,比较的是内容是否相同。
常量池:
指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据,包括类、方法、接口中的常量,也包括字符串直接量
String s0 = "hello";
String s1 = "hello";
String s2 = "he" + "llo";
System.out.println( s0 == s1 ); // true
System.out.println( s0 == s2 ); // true
System.out.println( s0.equals(s1) ); // true
这样下去,s0 == s1== s2 会一直相等下去,
(a) ==的判断,
(b) equals()的判断;
都相等,因为他们的地址都相等,因此只在常量池中有一份内存空间,地址全部相同;
String B = new String("ABC");
1
在内存堆中构造一个String对象,将新构造出来的String对象的引用赋给str。 因此 只要是new String(),则,栈中的地址都是指向最新的new出来的堆中的地址,
(a)“”==“” 是判断地址的,当然不相同;
(b)至于equals,String类型重写了 equals()方法,判断值是否相等,明显相等,因此 equals 是相等的;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。