夯实Java基础(十三)——字符串

字符串应该是我们在Java中用的最频繁、最多的,可见字符串对于我们来说是多么的重要,所以我们非常有必要去深入的了解一下。

1、String

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。

CharSequence与String都能用于定义字符串,但CharSequence的值是可读可写序列,而String的值是只读序列。

对于一个抽象类或者是接口类,不能使用new来进行赋值,但是可以通过以下的方式来进行实例的创建:

CharSequence cs="hello";

实例:

 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 }

运行结果:

可能是这样理解的吧,CharSequence是一个接口,本身是没有什么读写意义的。String只是它的一个实现类,虽然String是只读,但是CharSequence的实现类还有StringBuffer,StringBuilder这些可写的,所以用CharSequence作为参数可以接收String,StringBuffer,StringBuilder这些类型。

参考:https://blog.csdn.net/a78270528/article/details/46785949

2、String的实例和拼接

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()方法,则返回值就是在常量池中。

3、String中常用的方法

String中的常用方法我们需要熟练的掌握, 这样平时做一些字符串的常规操作就可以快速知道,而不用去查找API了。

①、常规:

  • int length():返回字符串的长度: return value.length
  • boolean isEmpty():判断是否是空字符串:return value.length == 0
  • String trim():返回字符串的副本,忽略前导空白和尾部空白
  • String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
  • String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
  • String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
  • char[] toCharArray():将字符串转为char型数组。
②、比较:
  • boolean equals(Object obj):比较字符串的内容是否相同
  • boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
  • int compareTo(String anotherString):比较两个字符串的大小
  • boolean matches(String regex):判断此字符串是否匹配给定的正则表达式。
③、查找:
  • char charAt(int index): 返回某索引处的字符return value[index]
  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引。注:indexOf和lastIndexOf方法如果未找到都是返回-1
  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
  • boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
  • boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
④、替换:
  • String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
  • String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
  • String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
  • String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
⑤、截取:
  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
  • String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
⑥、切片:
  • String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
  • String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

如果还想学习更多的方法,可以自行去看String的API。

4、StringBuffer和StringBuilder

StringBuffer和StringBuilder十分相似,它们都代表可变的字符序列,可以对字符序列进行增删改查操作,此时不会产生新的对象,而且它两内部的方法也是一样的。那么String、StringBuffer和StringBuilder的区别是什么。

String(JDK1.0):字符串常量,不可变字符序列,线程安全,效率低。

StringBuffer(JDK1.0):字符串变量,可变字符序列,线程安全,效率低。

StringBuilder(JDK5.0):字符串变量,可变字符序列,线程不安全,效率高。

为什么说String和StringBuffer是线程安全,StringBuilder是线程不安全呢?

因为String是final修饰的常量,它是不可变的字符串,所有的操作都是不可能改变它的值,所以线程是安全的。再通过看StringBuffer和StringBuilder的源码,可以很明显发现,StringBuffer是线程安全的,因为其下的所有方法都加上了synchronized。而StringBuilder则没有加这个关键字。

它们之间常用的方法:

  • StringBuffer append(xxx):用于进行字符串的拼接
  • cahr charAt(int index):返回char在指定索引在这个序列值。 
  • StringBuffer delete(int start,int end):删除指定位置的内容
  • StringBuffer deleteCharAt(int index):删除char在这个序列中的指定位置。 
  • StringBuffer replace(int start,int end,String str):把[start,end)位置上的元素替换为str
  • void sedtCharAt(int n,cahr ch):将指定索引位置的字符改成ch
  • StringBuffer insert(int offset,xxx):在指定位置插入xxx
  • StringBuffer reverse():将字符序列反转
  • int indexOf(String str):返回指定子字符串第一次出现的字符串内的索引。
  • int lastIndexOf(String str):返回指定子字符串最右边出现的字符串内的索引。 
  • String subString(int start,int end):返回一个新的 String,其中包含此序列中当前包含的字符的子序列。 

5、String、StringBuffer和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 就会开始工作,性能就会降低。

6、小结

通过上面的学习,我们简单小结一下:

String:适用于少量字符串操作的情况。

StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。

StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况。

原文地址:https://www.cnblogs.com/tanghaorong/p/11277009.html