String,StringBuffer,StringBulider的区别与联系
字符串常量池
如果想要学习String类相关知识,首先要了解一下什么是字符串常量池
在《深入理解java虚拟机》这本书上是这样写的:对于HotSpot虚拟机,根据官方发布的路线图信息,现在也有放弃永久代并逐步的改为采用Native Memory来实现方法区的规划了,在目前已经发布的JDK1.7的HotSpot中,已经把原来存放在方法区中的字符串常量池移出。根据查阅的资料显示在JDK1.7以后的版本中字符串常量池移到堆内存区域;同时在jdk1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域
在 JAVA 语言中有8中基本类型和一种比较特殊的类型String
。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。
通俗的去讲,常量池就类似于一个缓存,当创建该字符串的时候总是会先去常量池中查询是否已经存在,存在就可以拿来用,不存在就先创建
-
直接使用双引号声明出来的
String
对象会直接存储在常量池中。 -
如果不是用双引号声明的
String
对象,可以使用String
提供的intern
方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
String
上面已经描述了两种方法,下面我们来做具体的解析
-
直接双引号声明出来的String对象
public class StringPractice {
public static void main(String[] args) {
String str1="Hello";
String str2="Hello";
System.out.println(str1==str2);
}
}
运行结果:
解释:
采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"Hello"这个对象,如果不存在,则在字符串常量池中创建"Hello"这个对象,然后将池中"Hello"这个对象的引用地址返回给"Hello"对象的引用s1,这样s1会指向字符串常量池中"Hello"这个字符串对象;如果存在,则不创建任何对象,直接将池中"Hello"这个对象的地址返回,赋给引用s2。因为s1、s2都是指向同一个字符串池中的"Hello"对象,所以结果为true。
- 如果不是用双引号声明的
String
对象,可以使用String
提供的intern
方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
public class demo02 {
public static void main(String[] args) {
String str1=new String("Hello");
String str2=new String("Hello");
System.out.println(str1==str2);
}
}
运行效果:
解释:
采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"Hello"这个字符串对象,如果有,则不在池中再去创建"Hello"这个对象了,直接在堆中创建一个"Hello"字符串对象,然后将堆中的这个"Hello"对象的地址返回赋给引用s3,这样,s3就指向了堆中创建的这个"Hello"字符串对象;如果没有,则首先在字符串池中创建一个"Hello"字符串对象,然后再在堆中创建一个"Hello"字符串对象,然后将堆中这个"Hello"字符串对象的地址返回赋给s3引用,这样,s3指向了堆中创建的这个"Hello"字符串对象。s4则指向了堆中创建的另一个"Hello"字符串对象。s3 、s4是两个指向不同对象的引用,结果当然是false。
注意:这里我们补充一些关于==和equals()方法的区别
如果比较的为基本数据类型,==比较的为值是否相等,不可以用equals()
如果比较的为引用类型,==比较的依旧为值,但是引用类型是代表的地址,因此也可以说是==比较的是地址
equals()属于Object类的方法,其比较的也为地址,但是大部分类会重写该方法用来比较类的属性,比如String类,重写后比较的就是字符串内容
String一些简单的方法使用
import java.util.Locale;
public class demo3 {
public static void main(String[] args) {
String str1="HelloWorld I Love You Love Me";
//返回字符ch或子字符串str在字符串中第一次出现位置的索引
int a=str1.indexOf('a');
System.out.println("a:"+a);
//返回字符ch或子字符串str在字符串中最后一次出现位置的索引
int b=str1.lastIndexOf("Love");
System.out.println("b:"+b);
//返回字符串中index位置上的字符,其中index的取值范围是0~字符串长度-1
char c=str1.charAt(6);
System.out.println("c:"+c);
//判断此字符串是否以指定的字符串结尾
boolean d=str1.endsWith("Me");
System.out.println("d:"+d);
//返回此字符串的长度
int e=str1.length();
System.out.println("e:"+e);
//将此字符串与指定字符串比较
boolean f=str1.equals("lalala~");
System.out.println("f:"+f);
//判断此字符串是否以指定的字符串开始
boolean g=str1.startsWith("Hello");
System.out.println("g:"+g);
//判断此字符串是否包含指定的字符序列
boolean h=str1.contains("World");
System.out.println("h:"+h);
//将字符串中的字符都转换为小写字母
String i=str1.toLowerCase(Locale.ROOT);
System.out.println("i:"+i);
//将字符串中的字符都转换为大写字母
String j=str1.toUpperCase(Locale.ROOT);
System.out.println("j:"+j);
//将int变量i转换成字符串
String k=String.valueOf(5);
System.out.println("k:"+k);
//将此字符串转换为一个字符数组
char[] l=str1.toCharArray();
for(char m:l){
System.out.print("m:"+m);
}
System.out.println();
//返回一个字符串,用新字符串替换旧字符串
String n=str1.replace("World","Love");
System.out.println("n:"+n);
//根据参数将字符串分割
String[] o=str1.split(" ");
for (String p : o) {
System.out.print("p:"+p);
}
System.out.println();
//返回一个字符串,从指定位置开始到末尾
String q=str1.substring(1);
System.out.println("q:"+q);
//返回一个字符串,从指定位置开始,到指定位置-1结束
String r=str1.substring(1,6);
System.out.println("r:"+r);
//返回一个字符串,去除首尾空格
String s=str1.trim();
System.out.println("s:"+s);
}
}
//输出:
a:-1
b:22
c:o
d:true
e:29
f:false
g:true
h:true
i:helloworld i love you love me
j:HELLOWORLD I LOVE YOU LOVE ME
k:5
m:Hm:em:lm:lm:om:Wm:om:rm:lm:dm: m:Im: m:Lm:om:vm:em: m:Ym:om:um: m:Lm:om:vm:em: m:Mm:e
n:HelloLove I Love You Love Me
p:HelloWorldp:Ip:Lovep:Youp:Lovep:Me
q:elloWorld I Love You Love Me
r:elloW
s:HelloWorld I Love You Love Me
String为什么是不可变的?
我们可以看到,String类存储字符串是用字符数组存储的,同时也用了final进行了修饰,我们知道,final修饰的方法不能被重写,修饰的变量为基本类型的话不可变,为引用类型的话不可以指向其它对象,修饰的类不可以被继承。而这个字符数组是可以变的,只是不能指向其它对象而已。所以说根本原因是String类是final修饰的,不能被继承,同时该字符数组是被private和final修饰,也没有暴漏给外界可以更改的方法,因此不可变。
StringBuffer
由于字符串是常量,一旦创建就可以变了。若要是对字符串修改就得创建新的字符串,不方便。为此出现了StringBuffer类,StringBuffer的内容和长度都是可以变化的。
StringBuffer是继承AbstractStringBuilder类的
StingBuffer常用的方法
public class demo4 {
public static void main(String[] args) {
StringBuffer str=new StringBuffer();
//在字符串末尾添加新的字符串
str.append("Hello");
System.out.println("append之后:"+str);
//在指定位置插入字符串
str.insert(5,"World");
System.out.println("insert之后:"+str);
//删除指定位置的字符
str.deleteCharAt(5);
System.out.println("删除指定位置字符后:"+str);
//删除指定范围的字符数组
str.delete(1,3);
System.out.println("delete之后:"+str);
//替换指定的字符或字符数组
str.replace(2,3,"Love");
System.out.println("replace之后:"+str);
//返回字符串
System.out.println(str.toString());
//反转字符串
System.out.println("字符串反转"+str.reverse());
}
}
输出:
append之后:Hello
insert之后:HelloWorld
删除指定位置字符后:Helloorld
delete之后:Hloorld
replace之后:HlLoveorld
HlLoveorld
字符串反转dlroevoLlH
为什么StringBuffer是线程安全的
因为StringBuffer的方法绝大部分都加了synchronized修饰,同步锁,被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程会发生阻塞,直到当前线程访问完毕,其他线程才有机会执行该方法。
StringBulider
StringBulider与StringBuffer相似,最大的不同在于是非线程安全的,可以同步访问。
运行效率
StringBulider > StringBuffer >String
字符串拼接 “+”的使用及原理
StringBulider和StingBuffer不可以使用+来进行字符串拼接
String可以使用
这个可以看出StringBuffer在字符串拼接的时候出现报错;
“+”实际上是通过StringBulider调用append()方法实现的,拼接完成之后调用toString()得到一个String对象。
不过在循环内使用“+”进行字符串拼接的话,存在比较明显的缺陷,编译器不会创建单个StringBulider以复用,会导致创建过多的StringBulider对象