字符串应该是咱们在Java中用的最频繁、最多的,可见字符串对于咱们来讲是多么的重要,因此咱们很是有必要去深刻的了解一下。正则表达式
String就表明字符串,在Java中字符串属于对象。咱们刚刚接触Java时,在学习数据类型的时候应该提到过String。Java有基本数据类型和引用数据类型,而String就是一个引用数据类型,它是一个类,既然它是一个类,那咱们就来看看它的源码结构。数组
从上面的图能够看出,String类是用final修饰的,代表它不能再被继承了,由于String这个类中对字符串的操做的方法已经很是丰富,不须要咱们再去扩展功能了。同时还实现了序列化、比较排序、字符序列这三个接口,代表字符串能够被序列化、能够用于比较排序。关于实现了CharSequence接口下面有讲到。咱们还能够看到String类中定义了一个char型数组value[ ],这个是用于存储字符串的内容,而且使用final修饰的,代表这个是常量。因此字符串一旦被初始化,就不能够被改变,表示String是不可变性,这就致使每次对String的操做都会生成新的String对象。安全
关于String实现CharSequence接口这里,我去百度一下:多线程
CharSequence是一个接口,表示char值的一个可读序列。此接口对许多不一样种类的char序列提供统一的自读访问。此接口不修改该equals和hashCode方法的常规协定,所以,一般未定义比较实现 CharSequence 的两个对象的结果。他有几个实现类:CharBuffer、String、StringBuffer、StringBuilder。app
CharSequence与String都能用于定义字符串,但CharSequence的值是可读可写序列,而String的值是只读序列。性能
对于一个抽象类或者是接口类,不能使用new来进行赋值,可是能够经过如下的方式来进行实例的建立:学习
CharSequence cs="hello";测试
实例:ui
1 public class StringTest { 2 public static void main(String[] args) { 3 String str="String"; 4 StringBuffer sBuffer=new StringBuffer("StringBuffer"); 5 StringBuilder sBuilder=new StringBuilder("StringBuilder"); 6 show(str); 7 show(sBuffer); 8 show(sBuilder); 9 } 10 //若是参数类型为String则不能接收StringBuffer和StringBuilder 11 public static void show(CharSequence cs){ 12 System.out.println(cs); 13 } 14 }
运行结果:spa
多是这样理解的吧,CharSequence是一个接口,自己是没有什么读写意义的。String只是它的一个实现类,虽然String是只读,可是CharSequence的实现类还有StringBuffer,StringBuilder这些可写的,因此用CharSequence做为参数能够接收String,StringBuffer,StringBuilder这些类型。
参考:https://blog.csdn.net/a78270528/article/details/46785949
String的实例:
建立String的实例有两种方式,一种是直接给String的变量赋值,另外一种是使用String的构造器建立实例。那么这两种方式建立的实例有什么区别呢?区别就是前者会建立一个对象,然后者会建立两个对象。
举例:
1 public class StringTest { 2 public static void main(String[] args) { 3 //方式一:直接赋值 4 String s1="abc"; 5 String s2="abc"; 6 //方式二:new+构造器 7 String s3=new String("abc"); 8 String s4=new String("abc"); 9 System.out.println(s1 == s2);//true 10 System.out.println(s1 == s3);//false 11 System.out.println(s1 == s4);//false 12 System.out.println(s3 == s4);//false 13 System.out.println(s1.equals(s3));//true 14 } 15 }
运行结果一目了然,String的值是常量,它的值是放在方法区的处理池中,而常量池中相同的值在只会存在一份。咱们知道==比较的地址,equal()比较的是内容,s1和s2指向同一个引用,因此地址相同,而s3和s4它们分别建立了两个对象,地址值显然不一样。
经过这个图咱们也容易分析出建立实例时建立了几个对象。
String s1 = "abc"建立对象的过程:
首先检查常量池中是否存在内容为"abc"的字符串,若是有,则再也不建立对象,直接让s1变量指向该字符串的引用,若是没有则在常量池中建立"abc"对象而后让s1引用该对象。
String s3 = new String("abc")建立实例的过程:
首先在堆建立一个String的对象,并让s3引用指向该对象,而后再到常量池中查看是否存在内容为"abc"字符串对象,若是存在,则将String对象中的value引用指向常量对象,将new出来的字符串对象与字符串常量池中的对象联系起来,若是不存在,则在字符串常量池中建立一个内容为"abc"的字符串对象,并将堆中的String对象与之联系起来。
String的拼接:
字符串的拼接有三种方式:直接使用"+"、使用concat()方法、使用append()方法。这里我主要来讨论一下"+"问题,举例:
1 public class StringTest { 2 public static void main(String[] args) { 3 String s1="Hello"; 4 String s2="World"; 5 6 String s3="HelloWorld"; 7 String s4="Hello"+"World"; 8 String s5=s1+"World"; 9 String s6="Hello"+s2; 10 String s7=s1+s2; 11 12 System.out.println(s3==s4);//true 13 System.out.println(s3==s5);//false 14 System.out.println(s3==s6);//false 15 System.out.println(s3==s7);//false 16 System.out.println(s5==s6);//false 17 System.out.println(s5==s7);//false 18 System.out.println(s6==s7);//false 19 20 String s8=s7.intern(); 21 System.out.println(s3==s8);//true 22 23 } 24 }
从运行结果咱们能够得出结论:①、常量与常量的拼接结果是常量,它们在在常量池中完成。②、只要涉及到有变量的(很是量),结果都是在堆内存中完成的。③、若是拼接后的结果调用了intern()方法,则返回值就是在常量池中。
String中的经常使用方法咱们须要熟练的掌握, 这样平时作一些字符串的常规操做就能够快速知道,而不用去查找API了。
①、常规:
若是还想学习更多的方法,能够自行去看String的API。
StringBuffer和StringBuilder十分类似,它们都表明可变的字符序列,能够对字符序列进行增删改查操做,此时不会产生新的对象,并且它两内部的方法也是同样的。那么String、StringBuffer和StringBuilder的区别是什么。
String(JDK1.0):字符串常量,不可变字符序列,线程安全,效率低。
StringBuffer(JDK1.0):字符串变量,可变字符序列,线程安全,效率低。
StringBuilder(JDK5.0):字符串变量,可变字符序列,线程不安全,效率高。
为何说String和StringBuffer是线程安全,StringBuilder是线程不安全呢?
由于String是final修饰的常量,它是不可变的字符串,全部的操做都是不可能改变它的值,因此线程是安全的。再经过看StringBuffer和StringBuilder的源码,能够很明显发现,StringBuffer是线程安全的,由于其下的全部方法都加上了synchronized。而StringBuilder则没有加这个关键字。
它们之间经常使用的方法:
String、StringBuffer和StringBuilder涉及可变序列与不可变序列、线程是否安全状况,这些因素必然影响到它们之间的运行效率,因此咱们来比较一下他们之间的运行效率。
简单示例:
1 public class StringTest { 2 public static void main(String[] args) { 3 long startTime=0L; 4 long endTime=0L; 5 String text=" "; 6 StringBuffer buffer=new StringBuffer(""); 7 StringBuilder builder = new StringBuilder(""); 8 9 //String 10 startTime=System.currentTimeMillis(); 11 for (int i = 0; i < 50000; i++) { 12 text=text+i; 13 } 14 endTime=System.currentTimeMillis(); 15 System.out.println("String执行时间:"+(endTime-startTime)); 16 17 //StringBuffer 18 startTime=System.currentTimeMillis(); 19 for (int i = 0; i < 50000; i++) { 20 buffer.append(String.valueOf(i)); 21 } 22 endTime=System.currentTimeMillis(); 23 System.out.println("StringBuffer执行时间:"+(endTime-startTime)); 24 25 //StringBuilder 26 startTime=System.currentTimeMillis(); 27 for (int i = 0; i < 50000; i++) { 28 builder.append(String.valueOf(i)); 29 } 30 endTime=System.currentTimeMillis(); 31 System.out.println("StringBuilder执行时间:"+(endTime-startTime)); 32 } 33 }
运行结果可能须要等个5-8秒,运行结果以下:
咱们屡次运行的结果也大体相同,因此运行效率为:StringBuilder > StringBuffer > String。String如此的慢是由于它是字符串常量,在建立对象后是不可改变的,然而每次改变String类型的值都会在常量池中新建一个常量对象,因此很是耗时间。而StringBuffer和StringBuilder的可变的字符序列,它们只是在原有内容发生了改变,并无新建立对象。因此常常改变内容的字符串最好不要用 String类型,推荐使用StringBuffer,由于每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了之后, JVM 的 GC 就会开始工做,性能就会下降。
经过上面的学习,咱们简单小结一下:
String:适用于少许字符串操做的状况。
StringBuffer:适用多线程下在字符缓冲区进行大量操做的状况。
StringBuilder:适用于单线程下在字符缓冲区进行大量操做的状况。