字符串应该是我们在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";

实例:

 public class StringTest {
     public static void main(String[] args) {
         String str="String";
         StringBuffer sBuffer=new StringBuffer("StringBuffer");
         StringBuilder sBuilder=new StringBuilder("StringBuilder");
         show(str);
         show(sBuffer);
         show(sBuilder);
     }
     //如果参数类型为String则不能接收StringBuffer和StringBuilder
     public static void show(CharSequence cs){
         System.out.println(cs);
     }
 }

运行结果:

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

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

2、String的实例和拼接

String的实例:

创建String的实例有两种方式,一种是直接给String的变量赋值,另一种是使用String的构造器创建实例。那么这两种方式创建的实例有什么区别呢?区别就是前者会创建一个对象,而后者会创建两个对象。

举例:

 public class StringTest {
     public static void main(String[] args) {
         //方式一:直接赋值
         String s1="abc";
         String s2="abc";
         //方式二:new+构造器
         String s3=new String("abc");
         String s4=new String("abc");
         System.out.println(s1 == s2);//true
         System.out.println(s1 == s3);//false
         System.out.println(s1 == s4);//false
         System.out.println(s3 == s4);//false
         System.out.println(s1.equals(s3));//true
     }
 }

运行结果一目了然,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()方法。这里我主要来讨论一下"+"问题,举例:

 public class StringTest {
     public static void main(String[] args) {
         String s1="Hello";
         String s2="World";

         String s3="HelloWorld";
         String s4="Hello"+"World";
         String s5=s1+"World";
         String s6="Hello"+s2;
         String s7=s1+s2;

         System.out.println(s3==s4);//true
         System.out.println(s3==s5);//false
         System.out.println(s3==s6);//false
         System.out.println(s3==s7);//false
         System.out.println(s5==s6);//false
         System.out.println(s5==s7);//false
         System.out.println(s6==s7);//false

         String s8=s7.intern();
         System.out.println(s3==s8);//true

     }
 }

从运行结果我们可以得出结论:①、常量与常量的拼接结果是常量,它们在在常量池中完成。②、只要涉及到有变量的(非常量),结果都是在堆内存中完成的。③、如果拼接后的结果调用了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涉及可变序列与不可变序列、线程是否安全情况,这些因素必然影响到它们之间的运行效率,所以我们来比较一下他们之间的运行效率。

简单示例:

 public class StringTest {
     public static void main(String[] args) {
         long startTime=0L;
         long endTime=0L;
         String text=" ";
         StringBuffer buffer=new StringBuffer("");
         StringBuilder builder = new StringBuilder("");

         //String
         startTime=System.currentTimeMillis();
         for (int i = 0; i < 50000; i++) {
             text=text+i;
         }
         endTime=System.currentTimeMillis();
         System.out.println("String执行时间:"+(endTime-startTime));

         //StringBuffer
         startTime=System.currentTimeMillis();
         for (int i = 0; i < 50000; i++) {
             buffer.append(String.valueOf(i));
         }
         endTime=System.currentTimeMillis();
         System.out.println("StringBuffer执行时间:"+(endTime-startTime));

         //StringBuilder
         startTime=System.currentTimeMillis();
         for (int i = 0; i < 50000; i++) {
             builder.append(String.valueOf(i));
         }
         endTime=System.currentTimeMillis();
         System.out.println("StringBuilder执行时间:"+(endTime-startTime));
     }
 }

运行结果可能需要等个5-8秒,运行结果如下:

我们多次运行的结果也大致相同,所以运行效率为:StringBuilder > StringBuffer > String。String如此的慢是因为它是字符串常量,在创建对象后是不可改变的,然而每次改变String类型的值都会在常量池中新建一个常量对象,所以非常耗时间。而StringBuffer和StringBuilder的可变的字符序列,它们只是在原有内容发生了改变,并没有新创建对象。所以经常改变内容的字符串最好不要用 String类型,推荐使用StringBuffer,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,性能就会降低。

6、小结

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

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

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

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

夯实Java基础(十三)——字符串的更多相关文章

  1. 夯实Java基础系列目录

    自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...

  2. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...

  3. 夯实Java基础系列5:Java文件和Java包结构

    目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...

  4. 夯实Java基础系列7:一文读懂Java 代码块和执行顺序

    目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块 ...

  5. 夯实Java基础系列9:深入理解Class类和Object类

    目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...

  6. 夯实Java基础系列7:Java 代码块和执行顺序

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  7. 夯实Java基础(十一)——内部类

    1.内部类的概念 内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜 ...

  8. 夯实Java基础系列1:Java面向对象三大特性(基础篇)

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

  9. 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

    目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...

  10. 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

    目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...

随机推荐

  1. Python开发【第八篇】: 网络编程

    内容概要 楔子 软件开发架构 网络基础 套接字(socket) 粘包 socketserver模块 一. 楔子 现在有两个python文件a.py和b.py,分别运行,这两个程序之间需要传递一个数据, ...

  2. There is no getter for property named 'username' in 'class Model1.User'-----报错解决

    There is no getter for property named 'username' in 'class Model1.User' -----Model Model1.User'中没有名为 ...

  3. JVM中的本机内存跟踪

    1.概述 有没有想过为什么Java应用程序通过众所周知的-Xms和-Xmx调优标志消耗的内存比指定数量多得多?出于各种原因和可能的优化,JVM可以分配额外的本机内存.这些额外的分配最终会使消耗的内存超 ...

  4. django基础知识之状态保持session:

    状态保持 http协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态 客户端与服务器端的一次通信,就是一次会话 实现状态保持的方式:在客户端或服务器端存储与会话有关的数据 存储方式包括c ...

  5. 手机如何进入开发者选项--以vivo为例

    发现一个新方法  打开拨号键盘        输入    *#*#7777#*#* 欧儿了

  6. 前端动画 wow.js 效果

    让花里胡哨的特效变简单 wow.js动画class介绍 引入css样式以及js插件 <link rel="stylesheet" type="text/css&qu ...

  7. printf打印参数的顺序问题

    C语言的printf函数处理的参数顺序是从右向左的,例如如下程序: #include <stdio.h>    int main()  {      int a = 1, b = 2, c ...

  8. 关于导入别人的web项目,tomcat无法显示的问题

    这两天头大,老师讲了javaWeb项目,讲完就给我们发了代码,我就想导入直接用,结果它tomcat的add and remove 里一直没有这个项目名字 刚导入还报错,这个可能我的版本太低了,兼容不了 ...

  9. Eclipse安装STS插件

    由于Spring的配置文件较多,基于Eclipse配置也比较复杂.为了提高开发的效率,建议使用STS开发工具开发,或者在Eclipse安装一个STS插件. 在开发者配置bean的class时候能够根据 ...

  10. 用了三星Dex,我已经快一个月回家没开过电脑了

    其实比较早就知道手机使用显示屏扩展的功能,但是以前的技术可能受性能影响体验还不太好.后来让我期待的是Linux On Dex这个项目知道了手机已经如此强大了,可惜只能是是特定机器,因此在618之际乘着 ...