Java.lang中的String类和StringBuilder类介绍和常用方法¶
约 3366 个字 287 行代码 4 张图片 预计阅读时间 15 分钟
String类介绍¶
在Java中,String属于一种引用类型,表示字符串类型。实际上,在Java中,所有字符串字面量都是作为String类的对象实例化的,即使用双引号""包裹的都是String类的对象。
所以使用String s = "abc"表示的是创建了一个String类的对象内容为"abc",而s就是对象名,示意图如下:
根据官方文档对String类的描述,字符串都是常量,其值在创建后不可以被修改,所以在进行字符串拼接等对原始字符串内容进行的操作都是根据结果重新创建了一个新的字符串,例如下面的代码:
| Java | |
|---|---|
1 2 | |
当对象s指向了"hello"时,此时堆中存在一个空间,其内容为"hello",而接下来想在原来的字符串中拼接"world"是做不到的,所以将拼接后的结果存入一个新空间返回给s,示意图如下:
因为String类的对象是不可变的,所以当出现内容相同的String对象,则不会二次创建String对象,而是共享已经创建的String对象,例如下面的代码:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 | |
String类的底层成员介绍¶
基本介绍¶
String类之所以底层的数据不可以修改,是因为底层使用了final修饰了字符串数组,在JDK8时,String底层成员是private final char value[];,而JDK9开始,String底层的成员变为private final byte[] value;。修改存储类型的目的还是为了节省空间
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
回顾String传址调用问题¶
根据上面的原理,再回顾前面在「传值调用和传址调用」中遇到的String对象通过形参传递
首先,对于Java中的引用数据类型来说,都是传址调用,这里的传址调用表示在方法中可以修改对应形参的内容,例如形参是数组类型时,可以在方法中修改数组中的内容,此时会影响实参
对于String类型,因为其也属于引用数据类型,所以传递给形参,在方法中理论上也可以改变其内容,但是实际上因为String类的属性被final修饰,导致无法在方法中修改其对应的内容,进而不会影响到实参字符串中的内容
而对于直接将对象引用传递给形参本质是实参对象引用的副本,不论是基本数据类型还是引用数据类型,将实参对象引用直接传递形参后,在方法中改变形参对应的对象引用指向都不会影响到方法外的形参对应的实参对应的对象引用指向
例如上面代码中,首先因为字符串不可变,所以"修改字符串"实际上是一块新的String类对象,而arr1 = "修改字符串";只是改变了arr1在方法change中的指向,但是因为arr1是直接传递给形参,所以方法中改变arr1的指向不会影响main函数中的arr1的指向
String类对象的创建方式¶
在官方文档中,提供了String类的许多构造方式,下面主要介绍常用的几种:
- 使用无参构造创建
String类对象:String() - 使用字符串创建
String对象:String(String original) - 使用
char数组创建String对象:String(char[] value) - 使用平台的默认字符集解码指定的
byte数组,构造一个创建String对象:String(byte[] bytes)
Note
对于第4种创建方式来说,平台表示当前Java代码所在的操作系统,默认字符集表示当前操作系统所使用的字符编码格式,Windows下默认是GBK,解码表示根 据byte数组中的数值以及Java所在环境的编码格式进行与字符表对应的字符显示
需要注意,如果是在控制台使用javac编译Java代码,则使用的编码根据操作系统决定,Windows下是GBK,如果使用IDEA,则此时默认情况下UTF-8,因为IDEA启动编译时会自动加载一个启动参数(-Dfile.encoding=UTF-8),这个启动参数就是指定编码格式为UTF-8,解码时就需要使UTF-8
如果想修改解码方式,可以在IDEA中进行下面的方式改变编码格式:
File->Settings->展开Editor->选择File Encodings->根据自己需要改变全局编码(Global Encoding)和项目编码(Project Encoding)
下面是上面四种构造方式的使用实例(第4种方式以GBK为例):
Note
需要注意,在GBK编码中,一个中文字符占用2个字节,所以在byte数组中一个中文字符需要使用两个元素,而对于UTF-8编码,一个中文字符占用3个字节,所以在byte数组中一个中文字符需要使用三个元素
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
在实际使用String时,最常用的还是简写形式:
| Java | |
|---|---|
1 | |
两种特殊的构造String类对象的方式:
- 取出
char数组中的部分字符构造String类对象:String(char[] value, int offset, int count) - 取出
byte数组中的部分字符构造String类对象:String(byte[] bytes, int offset, int length)
Note
在上面的两种方式中,第一个参数代表对应数据类型的字符数组,第二个参数代表第一个包括的字符(即转换起点字符),第三个参数代表字符个数(从第二个参数代表的字符开始算起)
下面是实例代码:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
String面试题¶
创建对象or不创建对象¶
思考下面代码的结果:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
在上面的代码中,因为相同内容的字符串会共享,所以s1与s2实际上指向的是同一块空间,但是对于s3来说,因为使用了new关键字,所以在堆内存创建了一块新空间,但是这块空间的内容因为与s1和s2对应的字符串的内容相同,所以依旧是共享一块空间,但是s3指向的new开辟的新空间的地址,所以s3与s1和s2都不相同,示意图如下:
思考下面的代码结果:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
对于上面的代码来说,如果.class文件反编译成.java文件,就可以明白结果出现的原因,下面是反编译的结果:
可以看到,对于使用+两侧全是字符串常量直接拼接的,直接将拼接结果与其他字符串比对是否存在可共享的字符串,如果存在则直接使用;对于存在变量使用+进行拼接,则会调用StringBuilder()中的方法进行拼接,因为使用了new关键字,所以对象引用指向的空间地址并不是内容相同的字符串常量的地址
创建了几个对象and共有几个对象¶
思考下面的代码,回答:
- 对于第一行代码来说,创建了几个对象,共有几个对象
- 对于第二行代码来说,创建了几个对象,共有几个对象
| Java | |
|---|---|
1 2 3 | |
对于第一行代码来说,因为字符串在Java中属于String类的实例,即"abc"属于String类的对象,使s1指向了该对象,因此一共创建了1个对象,共有1个对象
对于第二行代码来说,首先"hello"会作为对象先创建,但是题目并没有说明在String s2 = new String("hello");之前是否已经创建了"hello"字符串对象,如果未创建,则包括new创建的对象在内一共创建了2个对象,共有2个对象;如果已经创建,根据字符串共享性,则包括new创建的对象在内一共创建了1个对象,共有2个对象
String常用方法¶
Note
需要注意,String类的方法不仅是对象引用变量可以调用,字符串常量也可以调用,因为字符串常量也是String类的对象
判断字符串是否相等方法¶
boolean equals(String s)方法:因为String类重写了父类Object中的equals()方法,所以此处比较的字符串中的内容是否相等boolean equalsIgnoreCase(String s)方法:与equals()方法效果基本一致,只是忽略字母大小写
使用实例如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Note
需要注意,上面的两个方法因为是对象调用,所以可能存在调用对象为空(null),为了避免出现空指针异常,也可以使用工具类java.util.Objects中的equals()方法进行比较,使用方式如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 | |
下面是Objects工具类中的equals()方法源码:
| Java | |
|---|---|
1 2 3 | |
获取字符串内容方法¶
-
获取字符串长度:
int length()Note
这里的获取长度是方法,不同于数组中的
length是数组的属性 -
(拼接)拼接调用对象字符串和参数字符串:
String concat(String s),返回一个新字符串的地址 - (字符下标)获取调用对象字符串中参数对应下标的字符:
char charAt(int index) - (查找字符)获取参数字符第一次在调用对象字符串中出现的位置:
int indexOf(String s) - (截取字符串)在调用对象字符串获取从参数位置开始(包括参数位置)之后的字符串,一直到最后一个字符:
String subString(int beginIndex),返回一个新字符串 - (截取字符串)在调用对象字符串获取从参数位置开始(包括参数位置)之后的字符串,一直到第二个参数的位置对应的字符前一个字符(含第一个参数对应的字符,不含第二个参数对应的字符):
String subString(int beginIndex,int endIndex),返回一个新字符串
基本使用实例如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
使用上面的方法遍历字符串:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 | |
字符串转换¶
- 将字符串转换成
char数组:char[] toCharArray() - 将字符串转换成
byte数组:byte[] getBytes() - 替换字符串中的字符:
String replace(CharSequence c1,CharSequence c2) - 按照指定的编码将字符串转成
byte数组:byte[] getBytes(String charsetName),注意本方法会抛出异常
Note
第三种方法中的CharSequence是String类实现的接口,所以根据多态(向上转型),可以直接传递String类对象
基本使用如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
根据指定字符分割字符串¶
使用方法:String[] split(String regex)
Note
注意,方法的参数是正则表达式,如果想使用.进行分割,需要对.进行转义:\\.;如果想使用\进行分割,则需要转义为:\\\\
使用实例如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
其他常用方法¶
- 调用对象字符串是否包含参数中的字符串:
boolean contains(String s) - 调用对象字符串是否以参数字符串结尾:
boolean endsWith(String s) - 调用对象字符串是否以参数字符串开头:
boolean startsWith(String s) - 调用字符串中的大写字母转小写(本身为小写的不改变):
String toLowerCase() - 调用字符串中的小写字母转大写(本身为大写的不改变):
String toUpperCase() - 去掉字符串首尾空格(不会去除字符串内容中的空格):
String trim()
基本使用如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
StringBuilder类介绍¶
与String不同的是,StringBuilder是一个可变的字符序列,并且该类提供了一个与StringBuffer兼容的一套API,但是不保证同步,导致线程不安全,但是效率高,一般用于字符串拼接
之所以使用StringBuilder类进行拼接而不是String拼接是因为StringBuilder类的字符串可以改变,所以拼接可以在原字符串上拼接,但是String必须创建一个新对象,如果拼接次数太多,String就会影响整体效率
StringBuilder原理¶
StringBuilder类底层自带一个缓冲区(没有被final修饰的byte数组)拼接字符串之后都会在此缓冲区中保存,在拼接的过程中,不会随意产生新对象,节省内存,其特点是:
- 底层自带缓冲区,此缓冲区是没有被
final修饰的byte数组,默认长度为16 - 如果字符串超出了已有的数组长度,数组会自动扩容,默认扩容大小是原来数组长度的2倍+2,但是如果扩容后的长度依旧不够,则会根据字符串的长度确定数组长度
StringBuilder的使用¶
StringBuilder对象创建方式¶
- 无参构造方法:
StringBuilder() - 使用
String构造StringBuilder对象:StringBuilder(String str)
使用实例如下:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
StringBuilder常用方法¶
- 字符串追加:
StringBuilder append(任意类型数据),与String拼接不同,在原字符串基础上拼接,不会创建新对象 - 字符串翻转:
StringBuilder reverse(),返回原字符串翻转后的结果,不会创建新对象 StringBuilder转String:String toString(),转换是为了便于后面使用String的方法
基本使用实例:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
方法实用:
判断一个字符串是否是「回文字符串」:
| Java | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
StringBuilder与StringBuffer的区别¶
相同点:用法一样,作用一样
不同点:
StringBuilder:拼接效率比StringBuffer和String高,但是线程不安全-
StringBuffer:效率比较底,但是线程安全 -
拼接效率:
StringBuilder>StringBuffer>String



