夯实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";
实例:
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基础(十三)——字符串的更多相关文章
- 夯实Java基础系列目录
自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...
- 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!
目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...
- 夯实Java基础系列5:Java文件和Java包结构
目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...
- 夯实Java基础系列7:一文读懂Java 代码块和执行顺序
目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块 ...
- 夯实Java基础系列9:深入理解Class类和Object类
目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); register ...
- 夯实Java基础系列7:Java 代码块和执行顺序
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...
- 夯实Java基础(十一)——内部类
1.内部类的概念 内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜 ...
- 夯实Java基础系列1:Java面向对象三大特性(基础篇)
本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...
- 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理
目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...
- 夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!
目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接 ...
随机推荐
- 在同一页面中显示多个echart图表
整理了一下大概有两种做法来实现在同一个页面中显示多个echart图表,废话不说直接上代码. 在同一个echart对象中绘制多个图表 <!DOCTYPE html> <html lan ...
- c# 开发ActiveX控件,添加事件,QT调用事件
c# 开发 ActiveX 的过程参考我的另一篇文章 : https://www.cnblogs.com/baqifanye/p/10414004.html 本篇讲如何 在C# 开发的ActiveX ...
- 从Spring的几个阶段理解其工作过程
Spring框架非常强大,想要彻底弄懂Spring是非常困难的. 为了便于了解Spring的工作原理,我们来研究一下,Spring是怎么加载的,Spring会经过几个阶段. 我们站在Javaweb ...
- iOS中 分类(category)与扩展(Extension)的区别?
1.分类(category)的作用 (1).作用:可以在不修改原来类的基础上,为一个类扩展方法.(2).最主要的用法:给系统自带的类扩展方法. 2.分类中能写点啥? (1).分类中只能添加“方法”,不 ...
- python generator与coroutine
python generator与coroutine 协程 简单介绍 协程,又称微线程,纤程,英文名Coroutine.协程是一种用户态的轻量级线程,又称微线程.协程拥有自己的寄存器上下文和栈,调度 ...
- Markdown下,上传图片问题
最简单的方法: 1,登录qq 2,登录博客园,并打开博客园添加随笔的地方:如图: 3,选择需要截屏的地方,按住ctrl+alt+A截屏,然后在qq的发送栏内贴过去 4,鼠标左键按住不松开,然后拖到这里 ...
- NioEventLoopGroup初始化
本文是我对Netty的NioEventLoopGroup及NioEventLoop初始化工作的源码阅读笔记, 如下图,是Netty的Reactor线程模型图,本文描述NioEventLoopGroup ...
- ElasticStack学习(八):ElasticSearch索引模板与聚合分析初探
一.Index Template与Dynamic Template的概念 1.Index Template:它是用来根据提前设定的Mappings和Settings,并按照一定的规则,自动匹配到新创建 ...
- CDQZ集训DAY4 日记
早上起来之后发现座位被zzh占了,得知座位改为先来后到,什么鬼…… 于是去了另一个有耳机的机房,然而并没有什么卵用. T1上来感觉很有意思,先切50分再说.T2好像是原题的说,切了原题30分后大胆猜测 ...
- 利用Docker搭建Redis集群
Redis集群搭建 运行Redis镜像 分别使用以下命令启动3个Redis docker run --name redis-6379 -p 6379:6379 -d hub.c.163.com/lib ...