目录
Java.lang中的String类和StringBuilder类介绍和常用方法
Java.lang
中的String
类和StringBuilder
类介绍和常用方法
String
类介绍
在Java中,String
属于一种引用类型,表示字符串类型。实际上,在Java中,所有字符串字面量都是作为String
类的对象实例化的,即使用双引号""
包裹的都是String类的对象。
所以使用String s = "abc"
表示的是创建了一个String类的对象内容为"abc"
,而s
就是对象名,示意图如下:
根据官方文档对String类的描述,字符串都是常量,其值在创建后不可以被修改,所以在进行字符串拼接等对原始字符串内容进行的操作都是根据结果重新创建了一个新的字符串,例如下面的代码:
String s = "hello";
s += "world";
当对象s
指向了"hello"
时,此时堆中存在一个空间,其内容为"hello"
,而接下来想在原来的字符串中拼接"world"
是做不到的,所以将拼接后的结果存入一个新空间返回给s
,示意图如下:
因为String
类的对象是不可变的,所以当出现内容相同的String
对象,则不会二次创建String
对象,而是共享已经创建的String
对象,例如下面的代码:
public class Test {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);
}
}
输出结果:
true
String
类的底层成员介绍
基本介绍
String
类之所以底层的数据不可以修改,是因为底层使用了final
修饰了字符串数组,在JDK8时,String
底层成员是private final char value[];
,而JDK9开始,String
底层的成员变为private final byte[] value;
。修改存储类型的目的还是为了节省空间
public class Call {
public static void main(String[] args) {
String arr1 = "字符串";
change(arr1);// 方法内的修改不影响main函数arr1
System.out.print(arr1);
}
public static void change(String arr1) {
arr1 = "修改字符串";
System.out.print(arr1);
System.out.println();
}
}
回顾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)
对于第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
为例):
需要注意,在GBK
编码中,一个中文字符占用2个字节,所以在byte
数组中一个中文字符需要使用两个元素,而对于UTF-8
编码,一个中文字符占用3个字节,所以在byte
数组中一个中文字符需要使用三个元素
public class Test {
public static void main(String[] args) {
// 1. 使用无参构造创建String类对象:String()
String str1 = new String();
System.out.println(str1);
// 2. 使用字符串创建String对象:String(String original)
String str2 = new String("hello");
System.out.println(str2);
// 3. 使用char数组创建String对象:String(char[] value)
char[] chars = {'a', 'b', 'c'};
String str3 = new String(chars);
System.out.println(str3);
// 4. 使用平台的默认字符集解码指定的 byte 数组,构造一个创建String对象:String(byte[] bytes)
// ASCII 码
byte[] bytes = {97, 98, 99};
String str4 = new String(bytes);
System.out.println(str4);
// GBK 编码
byte[] bytes1 = {-60, -29, -70, -61};
String str5 = new String(bytes1);
System.out.println(str5);
}
}
输出结果:
hello
abc
abc
你好
在实际使用String
时,最常用的还是简写形式:
String 对象名 = "字符串"
两种特殊的构造String
类对象的方式:
- 取出
char
数组中的部分字符构造String
类对象:String(char[] value, int offset, int count)
- 取出byte数组中的部分字符构造String类对象:
String(byte[] bytes, int offset, int length)
在上面的两种方式中,第一个参数代表对应数据类型的字符数组,第二个参数代表第一个包括的字符(即转换起点字符),第三个参数代表字符个数(从第二个参数代表的字符开始算起)
下面是实例代码:
public class Test {
public static void main(String[] args) {
// 1. 取出char数组中的部分字符构造String类对象:String(char[] value, int offset, int count)
char[] chars1 = {'a', 'b', 'c', 'd', 'e'};
String str6 = new String(chars1, 1, 3);
System.out.println(str6);
// 2. 取出byte数组中的部分字符构造String类对象:String(byte[] bytes, int offset, int length)
byte[] bytes1 = {-60, -29, -70, -61};
String str7 = new String(bytes1, 0, 2);
System.out.println(str7);
}
}
输出结果:
bcd
你
String面试题
创建对象or不创建对象
思考下面代码的结果:
public class Demo04String {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1==s2); //true
System.out.println(s1==s3); //false
System.out.println(s2==s3); //false
}
}
在上面的代码中,因为相同内容的字符串会共享,所以s1
与s2
实际上指向的是同一块空间,但是对于s3
来说,因为使用了new
关键字,所以在堆内存创建了一块新空间,但是这块空间的内容因为与s1
和s2
对应的字符串的内容相同,所以依旧是共享一块空间,但是s3
指向的new
开辟的新空间的地址,所以s3
与s1
和s2
都不相同,示意图如下:
思考下面的代码结果:
public class Test02 {
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 = s1+s2;
System.out.println(s3==s4); //true
System.out.println(s3==s5); //false
System.out.println(s3==s6); //false
}
}
对于上面的代码来说,如果.class
文件反编译成.java
文件,就可以明白结果出现的原因,下面是反编译的结果:
可以看到,对于使用+
两侧全是字符串常量直接拼接的,直接将拼接结果与其他字符串比对是否存在可共享的字符串,如果存在则直接使用;对于存在变量使用+
进行拼接,则会调用StringBuilder()
中的方法进行拼接,因为使用了new
关键字,所以对象引用指向的空间地址并不是内容相同的字符串常量的地址
创建了几个对象and共有几个对象
思考下面的代码,回答:
- 对于第一行代码来说,创建了几个对象,共有几个对象
- 对于第二行代码来说,创建了几个对象,共有几个对象
String s1 = "abc"; // 1
String s2 = new String("hello"); // 2
对于第一行代码来说,因为字符串在Java中属于String
类的实例,即"abc"
属于String
类的对象,使s1
指向了该对象,因此一共创建了1个对象,共有1个对象
对于第二行代码来说,首先"hello"
会作为对象先创建,但是题目并没有说明在String s2 = new String("hello");
之前是否已经创建了"hello"
字符串对象,如果未创建,则包括new
创建的对象在内一共创建了2个对象,共有2个对象;如果已经创建,根据字符串共享性,则包括new
创建的对象在内一共创建了1个对象,共有2个对象
String常用方法
需要注意,String
类的方法不仅是对象引用变量可以调用,字符串常量也可以调用,因为字符串常量也是String
类的对象
判断字符串是否相等方法
boolean equals(String s)
方法:因为String
类重写了父类Object
中的equals()
方法,所以此处比较的字符串中的内容是否相等boolean equalsIgnoreCase(String s)
方法:与equals()
方法效果基本一致,只是忽略字母大小写
使用实例如下:
public class Test03 {
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
String s3 = "Abc";
System.out.println(s1==s2);//比较地址值
//boolean equals(String s)
System.out.println(s1.equals(s2));
//boolean equalsIgnoreCase(String s)
System.out.println(s1.equalsIgnoreCase(s3));
}
}
需要注意,上面的两个方法因为是对象调用,所以可能存在调用对象为空(null
),为了避免出现空指针异常,也可以使用工具类java.util.Objects
中的equals()
方法进行比较,使用方式如下:
private static void method(String s1, String s2) {
// 使用工具类Objects中的equals方法
if (Objects.equals(s1, s2)) {
System.out.println("相同字符串");
} else{
System.out.println("不相同字符串");
}
}
下面是Objects
工具类中的equals()
方法源码:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
获取字符串内容方法
- 获取字符串长度:
int length()
这里的获取长度是方法,不同于数组中的
length
是数组的属性
- (拼接)拼接调用对象字符串和参数字符串:
String concat(String s)
,返回一个新字符串的地址 - (字符下标)获取调用对象字符串中参数对应下标的字符:
char charAt(int index)
- (查找字符)获取参数字符第一次在调用对象字符串中出现的位置:
int indexOf(String s)
- (截取字符串)在调用对象字符串获取从参数位置开始(包括参数位置)之后的字符串,一直到最后一个字符:
String subString(int beginIndex)
,返回一个新字符串 - (截取字符串)在调用对象字符串获取从参数位置开始(包括参数位置)之后的字符串,一直到第二个参数的位置对应的字符前一个字符(含第一个参数对应的字符,不含第二个参数对应的字符):
String subString(int beginIndex,int endIndex)
,返回一个新字符串
基本使用实例如下:
public class Test03 {
public static void main(String[] args) {
String s1 = "abcdefg";
//int length()
System.out.println(s1.length());
//String concat(String s)
System.out.println(s1.concat("haha"));
//char charAt(int index)
System.out.println(s1.charAt(0));
//int indexOf(String s)
System.out.println(s1.indexOf("a"));
//String subString(int beginIndex)
System.out.println(s1.substring(3));
//String subString(int beginIndex,int endIndex)
System.out.println(s1.substring(1, 6));
}
}
输出结果:
7
abcdefghaha
a
0
defg
bcdef
使用上面的方法遍历字符串:
public class Test03 {
public static void main(String[] args) {
String s = "abcdefg";
for (int i = 0; i < s.length(); i++) {
System.out.println(s.charAt(i));
}
}
}
字符串转换
- 将字符串转换成
char
数组:char[] toCharArray()
- 将字符串转换成
byte
数组:byte[] getBytes()
- 替换字符串中的字符:
String replace(CharSequence c1,CharSequence c2)
- 按照指定的编码将字符串转成
byte
数组:byte[] getBytes(String charsetName)
,注意本方法会抛出异常
第三种方法中的CharSequence
是String
类实现的接口,所以根据多态(向上转型),可以直接传递String
类对象
基本使用如下:
public class Test04 {
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "abcdefg";
//1.char[] toCharArray()
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.print(chars[i] + " ");
}
System.out.println();
//2.byte[] getBytes()
byte[] bytes = s.getBytes();
for (int i = 0; i < bytes.length; i++) {
System.out.print(bytes[i] + " ");
}
System.out.println();
//3.String replace(CharSequence c1,CharSequence c2)
System.out.println(s.replace("a","z"));
//4.byte[] getBytes(String charsetName)
byte[] bytes1 = "你好".getBytes("GBK");
for (int i = 0; i < bytes1.length; i++) {
System.out.print(bytes1[i] + " ");
}
}
}
输出结果:
a b c d e f g
97 98 99 100 101 102 103
zbcdefg
-60 -29 -70 -61
根据指定字符分割字符串
使用方法:String[] split(String regex)
注意,方法的参数是正则表达式,如果想使用.
进行分割,需要对.
进行转义:\\.
使用实例如下:
public class Test05 {
public static void main(String[] args) {
String s = "abc,txt";
String[] split = s.split(",");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
String s2 = "haha.hehe";
String[] split1 = s2.split("\\.");
for (int i = 0; i < split1.length; i++) {
System.out.println(split1[i]);
}
}
}
输出结果:
abc
txt
haha
hehe
其他常用方法
- 调用对象字符串是否包含参数中的字符串:
boolean contains(String s)
- 调用对象字符串是否以参数字符串结尾:
boolean endsWith(String s)
- 调用对象字符串是否以参数字符串开头:
boolean startsWith(String s)
- 调用字符串中的大写字母转小写(本身为小写的不改变):
String toLowerCase()
- 调用字符串中的小写字母转大写(本身为大写的不改变):
String toUpperCase()
- 去掉字符串首尾空格(不会去除字符串内容中的空格):
String trim()
基本使用如下:
public class Test06 {
public static void main(String[] args) {
String s = "abcdefg";
//1.boolean contains(String s)
System.out.println(s.contains("a"));
//2.boolean endsWith(String s)
System.out.println(s.endsWith("g"));
//3.boolean startsWith(String s)
System.out.println(s.startsWith("a"));
//4.String toLowerCase()-> 将字母转成小写
System.out.println("ADbcda".toLowerCase());
//5.String toUpperCase() -> 将字母转成大写
System.out.println("dafadRWERW".toUpperCase());
//6.String trim()
System.out.println(" hadfhad hdsfha sfhdsh ".trim());
// 拓展:使用替换字符串内容中的空格
System.out.println("hadfhad hdsfha sfhdsh".replace(" ",""));
}
}
输出结果:
true
true
true
adbcda
DAFADRWERW
hadfhad hdsfha sfhdsh
hadfhadhdsfhasfhds
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)
使用实例如下:
public class Test06 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
System.out.println(sb);
StringBuilder sb1 = new StringBuilder("abc");
System.out.println(sb1);
}
}
StringBuilder
常用方法
- 字符串追加:
StringBuilder append(任意类型数据)
,与String
拼接不同,在原字符串基础上拼接,不会创建新对象 - 字符串翻转:
StringBuilder reverse()
,返回原字符串翻转后的结果,不会创建新对象 StringBuilder
转String
:String toString()
,转换是为了便于后面使用String
的方法
基本使用实例:
public class Test {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
StringBuilder sb1 = sb.append("abc");
System.out.println(sb1);
System.out.println(sb);
System.out.println(sb==sb1);
//链式调用
sb.append("def").append("g").append("f");
System.out.println(sb);
sb.reverse();
System.out.println(sb);
String s = sb.toString();
System.out.println(s);
}
}
输出结果:
abc
abc
true
abcdefgf
fgfedcba
fgfedcba
方法实用:
判断一个字符串是否是「回文字符串」:
public class Test01 {
public static void main(String[] args) {
String str = "abcba";
StringBuilder sb = new StringBuilder(str);
// 使用StringBuilder中的reverse方法反转字符串,反转完后转换为String使用其equals方法判断内容是否相同,如果反转与原字符串相等,则是回文串
boolean result = sb.reverse().toString().equals(str);
System.out.println(result);
}
}
StringBuilder
与StringBuffer
的区别
相同点:
用法一样,作用一样
不同点:
StringBuilder
:拼接效率比StringBuffer
和String
高,但是线程不安全StringBuffer
:效率比较底,但是线程安全
- 拼接效率:
StringBuilder
>StringBuffer
>String