2.String类
特点不可变性 final-操作量较少
String类、StringBuilder类、StringBuffer类是三个字符串相关类。
String类的对象代表不可变的字符序列,StringBuilder类和StringBuffer类代表可变字符序列
2.1 String类的理解
2.1.1. 类的声明
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {}
final:String类是不可以被继承的
Serializable:可序列化接口。只要是实现此接口的对象就可以通过网络 或本地流进行数据的传输;
Comparable:只要是实现此接口的类,其对象都可以比较大小。
2.1.2. 内部声明的属性
/**Jdk1.8中:
1.存储字符串数据的容器
2.final:指明此 value数组一旦初始化,其地址就不可变。 */
private final char value[];
/**Jdk1.9开始:为了节省内部空间
1.存储字符串数据的容器 */
private final byte value[];
2.2 String的内存结构
字符串常量都存储在字符串常量池(StringTable)中;
字符串常量池:不允许存放两个相同的字符串常量
字符串常量池:在不同 Jdk 版本中存储位置不同:
(1)jdk1.7之前: 字符串常量池存放在方法区(jdk1.8称其为元空间)
(2)jdk1.7之后(包括jdk1.7):存放在堆空间
2.2.1 不可变性的理解
package com.kString;
import org.junit.Test;
public class String0 {
@Test
public void test1() {
String str1 = "hello";// 字面量的定义
String str2 = "hello";
// 字面量相同时使用同一个对象,即引用的地址相同
System.out.println(str1 == str2);// 结果,true
}
@Test
public void test2() {
/**
* String的不可变性
* ①当对字符串变量重新赋值时,需要重新指定一个字符串常量的
* 位置进行赋值,不能在原有的位置修改;
*/
String str1 = "hello";
String str2 = "hello";
str2 = "hi";
System.out.println(str1);// 结果,hello
System.out.println(str2);// 结果,hi
}
@Test
public void test3() {
/**
* String的不可变性;
* ②在对现有字符串进行拼接操作时,需要重新开辟空间保存拼接
* 以后的字符串,不能在原有的位置修改;
*/
String str1 = "hello";
String str2 = "hello";
/* 在`堆`中开辟了一处新空间,str2指向这个新空间
String str2 = new String();
str2 = str2 + " hi";
*/
str2 += " hi";
System.out.println(str1);// 结果,hello
System.out.println(str2);// 结果,hello hi
}
/**
* String的不可变性;
* ③当调用字符串的 replace()替换现有的字符时,需要重新开辟空间后,
* 保存修改后的字符串,不能在原有的位置修改;*/
@Test
public void test4() {
String str1 = "hello";
String str2 = "hello";
/* 取代:replace(char oldChar, char newChar)
返回的是一个新的 字符串*/
String str3 = str2.replace('l', 'o');
System.out.println(str1);//hello
System.out.println(str2);//hello
System.out.println(str3);//heooo
}
}
不可变性练习
String str="good";
char[] ch = {'t','e','s','t'};
// 传参时:基本数据类型传的是值,引用数据类型传的是地址
public void change(String str, char[] ch){
// 这里是 new了一个对象用来存储改变后的值
str="test ok";
System.out.println(str);// 结果,test ok
ch[0]='b';
}
@Test
public void test10(){
String0 s0 = new String0();
s0.change(s0.str,s0.ch);
System.out.println(s0.str);// 结果,good
System.out.println(s0.ch);// 结果,best
}
2.2.2 String的2种实例化方式
String实例化的两种方式:
(1) String str1 = “hello”;
(2) String str2 = new String(“hello”);
package com.kString;
import org.junit.Test;
public class String0 {
@Test
public void test5() {
String str1 = "hello";
String str2 = "hello";
String s1 = new String("hello");
String s2 = new String("hello");
//==:比较对象时是地址; equals比较的是内容
System.out.println(str1 == str2);//true
System.out.println(str1 == s1);//false
System.out.println(s1 == s2);//false
System.out.println(str1.equals(s1));//true
System.out.println(s1.equals(s2));//true
}
}
思考:String s1 = new String(“hello”); 在内存中创建了几个对象?
两个对象:①在堆空间 new的对象;②在字符串常量池中生成的字面量
package com.kString;
import org.junit.Test;
class Person{
String name;
}z
public class String0 {
@Test
public void test6() {
Person p1 = new Person();
Person p2 = new Person();
/*
① p1和 p2在堆中的地址不同,在常量池中指向的值都是 Tom
②常量池添加新的字面量 Jerry;
③将 p1指向的地址变为 Jerry的地址 */
p1.name= "Tom";
p2.name= "Tom";
p1.name= "Jerry";
System.out.println(p1.name);// 结果,Jerry
System.out.println(p2.name);// 结果,Tom
}
}
2.2.3 String的连接操作
常量 + 常量
:结果仍存储在字符串常量池中,返回此字面量的地址。注:此时的常量可能是字面量,也可能是final修饰的常量;常量 + 变量
;变量 + 常量
:都会通过 new的方式创建一个新的字符串,返回的是堆空间中此字符串对象的地址,和concat(String otherString)一样创建新的对象;- 调用
String.intern()
:返回的是字符串在常量池中字面量的地址,而不是对象的地址。
package com.kString;
import org.junit.Test;
/**情况Ⅰ~Ⅲ */
public class String0 {
@Test
public void test7() {
String s1 = "Hello";
String s2 = "World";
String s3 = "HelloWorld";
String s4 = "Hello" + "World";
/*底层new 了一个StringBuilder调用了它的toString() 方法,
就是在该方法中 new了一个新对象*/
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=s5.intern();
System.out.println(s4 == s8);// 结果,true
}
@Test
public void test8() {
final String s1 = "Hello";
final String s2 = "World";
String s4 = "Hello" + "World";
String s5 = s1 + "World";
String s6 = "Hello" + s2;
String s7 = s1 + s2;
System.out.println(s4 == s5);// 结果,true
System.out.println(s4 == s6);// 结果,true
System.out.println(s4 == s7);// 结果,true
System.out.println(s5 == s6);// 结果,true
System.out.println(s5 == s7);// 结果,true
System.out.println(s6 == s7);// 结果, true
}
}
- (了解):
String.concat()
:不管是常量还是变量,参数是常量(或是变量),只要调用此方法,都返回一个 new的新对象。
package com.kString;
import org.junit.Test;
/**4. String.concat(); */
public class String0 {
@Test
public void test9() {
String s1 = "Hello";
String s2 = "World";
String s3 = s1.concat(s2);
String s4 = "Hello".concat("World");
String s5 = s1.concat("World");
System.out.println(s3 ==s4);//false
System.out.println(s3 ==s5);//false
System.out.println(s4 ==s5);//false
}
}
2.3 String的常用API_1
2.3.1 String类构造器
public String(){/*初始化一个新创建的 String对象,以使其表示空字符序列 */}
public String(String original){/*初始化一个新创建的 String对象,使其表示一个与参数形同的字符序列*/}
public String(char value[]){/* 通过当前参数中的字符数组,来构建新的 String */}
public String(char value[], int offset, int count){/* 通过字符数组的一部分,来构建新的 String */}
public String(byte[] bytes){/* 通过使用平台的`默认字符集`,解码当前参数中的 字节数组来构建新的 String */}
public String(byte[] bytes,String charsetName){/* 通过使用指定的字符集,解码当前参数中的字节数组,来构建新的 String */}
/**
* String构造器的使用
*/
@Test
public void test1() {
String s1 = new String();
String s2 = new String("");
String s3 = new String(new char[]{'a', 'b', 'c'});
System.out.println(s3);// 结果,abc
}
2.3.2 String与常见的其他结构之间的转换
(1)字符集:
- 在UTF-8字符集中:一个汉字占用3个字节,一个字母占1个字节
- 在GBK字符集中:一个汉字占用2个字节,一个字母占用1个字节
- utf-8和gbk 都向下兼容 ASCII码
(2)编码与解码
- 编码:String —> 字节或字节数组
- 解码:字节或字节数组 —> String
- 要求:解码字符集 需要和编码字符集相同,否则就会出现乱码
1. String和基本数据类型、包装类之间的转换:
- 基本数据类型 --> String:
valueOf(num)
- String --> 基本数据类型: 调用包装类的
parseXxx(String s)
@Test
public void test2() {
/* 1.基本数据类型 --> String */
int num = 10;
// 方式一:
String s1 = num + "";
// 方式二:
String s2 = String.valueOf(num);
System.out.println(s1.getClass());// 结果,class java.lang.String
System.out.println(s2.getClass());// 结果,class java.lang.String
/* 2.String --> 基本数据类型:调用包装类的parseXxx(String s) */
int i = Integer.parseInt(s1);
System.out.println(i);// 10
}
2. String和 char[] 之间的转换:
- String —> char[],调用
String.toCharArray()
- char[] —> String,调用
String的构造器
@Test
public void test3() {
/*1.String ---> char[],调用 String.toCharArray() */
String s1 = "Tom and Jerry";
char[] chars = s1.toCharArray();
for (char c : chars) {
System.out.print(c);
}
System.out.println();
/*2.char[] ---> String,调用 String的构造器 */
String s = new String(chars);
System.out.println("s: " + s);
}
3. String和 byte[] 之间的转换:
- String 转 byte[],调用 String.getBytes()方法
- byte[] 转 String,调用 String的构造器
@Test
public void test4() throws UnsupportedEncodingException {
/* 1.String 转 byte[],调用 String.getBytes()方法 */
String s1 = new String("c国");
byte[] bytes = s1.getBytes();
for (byte b : bytes) {
System.out.println(b);// 转成ASCII码
}
// getBytes(String charsetName); 使用指定的字符集编码方式;
byte[] gbkByte = s1.getBytes("gbk");
for (byte b : gbkByte) {
System.out.println(b);
}
/* 2.byte[] 转 String,调用 String的构造器 */
String s2 = new String(bytes);// 使用默认的解码字符集:utf-8
String s3 = new String(gbkByte,"utf-8");// 使用显示的解码字符集:utf-8
System.out.println(s2 +"---"+s3);// 结果,c国---c��
// 使用显示的解码字符集:gbk
System.out.println(new String(gbkByte,"gbk"));// c国
}
2.3.3 练习一:反转String
将一个字符串进行翻转,将字符串中指定部分进行翻转,
如:“abcdefg"反转为"abfedcg”
@Test
public void test11() {
String sc = "abcdefg";
String s1 = new String0().reverse1(sc, 2, 5);
String s2 = reverse2(sc, 2, 5);
System.out.println("Method1: "+s1);
System.out.println("Method2: "+s2);
}
方法一:将字符串转为char[],针对数组进行相应位置的反转,然后将char[]转为 String
public String reverse1(String str, int fromIndex, int toIndex) {
char[] arr = str.toCharArray();
for (int i = fromIndex, j = toIndex; i < j; i++, j--) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//使用构造器
return new String(arr);
}
方法二:将字符串分成三部分:对中间部分进行从后往前遍历,再拼接
public String reverse2(String str, int fromIndex, int toIndex) {
//1.获取 str的第一部分
String finalStr = str.substring(0, fromIndex);
//2.拼接第二部分
for (int i = toIndex; i >= fromIndex; i--) {
finalStr += str.charAt(i);
}
//3.拼接第三部分
finalStr += str.substring(toIndex + 1);
return finalStr;
}
两种方法分析:
方法一:没有过多的占用内存,主要操作都在数组中进行的,不需要另外开辟空间;
方法二:当需要操作的字符串比较大时,会占用较多内存,效率变慢
方法三:使用StringBuffer
2.3.4 练习二:获取String出现的次数
练习二:获取一个字符串在另一个字符串中出现的次数
如:获取 “ab”,在 "serabthabwnabbyabsebab"中出现的次数
@Test
public void test12() {
String s="serabthabwnabbyabsebab";
int i = getSubStringCount(s, "ab");
System.out.println(i);
}
/**
* @description 判断 subStr 在 str中出现的次数
* @param str
* @param subStr 子字符串
* @return 返回次数
*/
public int getSubStringCount(String str, String subStr) {
int count = 0;// 记录出现的次数
if (str.length() >= subStr.length()){
int index = str.indexOf(subStr);
while (index >= 0) {
count++;
index = str.indexOf(subStr, index + subStr.length());
}
}
return count;
}
2.4 String的常用API_2
2.4.1 常用方法
- boolean isEmpty(); 判断字符串是否为空
- int length(); 返回字符串长度
- String concat(xx): 拼接字符 xx
- boolean equals(Object obj); 比较字符串是否相等
- boolean equalsIgnoreCase(Object obj); 比较字符串是否相等,不区分大小写
String s1 = "";
String s2 = "tom";
String s3 = "Tom";
@Test
public void test1() {
/** 1. boolean isEmpty(); 判断字符串是否为空 */
String str1 = new String();
String str2 = new String("");
// s1:true; str1:true; str2:true
System.out.println("s1:" + s1.isEmpty() + "; str1:" + str1.isEmpty() + "; str2:" + str2.isEmpty());
String str3 = null;
// NullPointerException 报空指针异常
// System.out.println(str3.isEmpty());
/** 2. int length(); 返回字符串长度 */
System.out.println(s2.length());// 结果,3
/**3. String concat(xx): 拼接字符 xx */
System.out.println(s3.concat(" is in China"));//Tom is in China
/**4. boolean equals(Object obj); 比较字符串是否相等 */
if (s2.equals("tom")) {
System.out.println("s2=\"tom\"");//s2="tom"
}
/**5. boolean equalsIgnoreCase(Object obj);比较字符串是否相等,不区分大小写*/
if (s2.equalsIgnoreCase(s3)) {
System.out.println("s2=s3");//s2=s3
}
}
- int compareTo(String other): 比较字符串大小,区分大小写,按照
Unicode编码
比较大小- int compareToIgnoreCase(String other): 比较字符串大小,不区分大小写
- String toLowerCase(); 将字符串中大写字母转为小写字母
- String toUpperCase(); 将字符串中小写字母转为大写字母
- Stirng trim(): 去掉字符串前后空白符
- public String intern(): 结果在常量池中共享
String s2 = "tom";
String s3 = "Tom";
String s4 = "中国";
String s5 = " Jerry Love ";
@Test
public void test2() {
/**6. int compareTo(String other): 比较字符串大小(实现了Comparable接口),
* 区分大小写,按照`Unicode编码`比较大小*/
System.out.println("s2-s3=" + s2.compareTo(s3));//s2-s3=32
/**7. int compareToIgnoreCase(String other): 比较字符串大小,不区分大小写*/
System.out.println("s2-s3=" + s2.compareToIgnoreCase(s3));//s2-s3=0
/**8. String toLowerCase(); 将字符串中大写字母转为小写字母 */
System.out.println(s3.toLowerCase());//tom
System.out.println(s4.toLowerCase());//中国
/**9. String toUpperCase(); 将字符串中小写字母转为大写字母*/
System.out.println(s3.toUpperCase());//TOM
System.out.println(s4.toUpperCase());//中国
/**10. Stirng trim(): 去掉字符串前后空白符*/
System.out.println("\""+s5.trim()+"\"");//"Jerry Love"
}
2.4.2 查找字符串
1)查找特定字符串,返回
Boolean值
12. boolean contains(xx): 是否包含xx2)查找特定字符,返回
字符所在位置下标
13. int indexOf(xx); // 从前往后查找字符串中xx,如果有返回第一次出现的下标,没有找到返回 -1;
14. int indexOf(String str, int fromIndex); // 返回子字符串在此字符串中 第一次出现的下标,从指定下标处开始搜索;
15. int lastIndexOf(xx);//从后往前查找当前字符串中xx,如果有返回最后一次出现的下标,没有找到返回 -1;
16. int lastIndexOf(String str, int fromIndex); 返回子字符串在此字符串中 最后一次出现的下标,从指定下标处开始反向搜索;
@Test
public void test1(){
String str= "hello,World";
/*13. 从前往后查找字符串中xx,如果有返回第一次出现的下标,没有找到返回 -1;*/
System.out.println(str.indexOf("Wo"));//6
/*14. 返回子字符串在此字符串中 第一次出现的下标,从指定下标处开始搜索;*/
System.out.println(str.indexOf("lo",2));//3
/*15. 从后往前查找当前字符串中xx,如果有返回最后一次出现的下标,没有找到返回 -1;*/
System.out.println(str.lastIndexOf("el"));//1
/*16. 返回子字符串在此字符串中 最后一次出现的下标,从指定下标处开始反向搜索;*/
System.out.println(str.lastIndexOf("ll", 9));//2
}
2.4.3 截取字符串
截取字符串,返回截取后的字符串(截取内容,前包后不包)
17. String substring(int beginIndex);
String substring(int beginIndex, int endIndex); 注意取值范围是:[ beginIndex , endIndex )
String lastIndexOf(int ch);//从特定字符处截取字符串,返回后面的字符串
package com.kString;
import java.util.UUID;
/** 2.查找特定字符,返回字符所在位置下标*/
public class String2 {
public static void main(String[] args) {
String str= "hello,World";
/** 17.截取字符串,返回截取后的字符串(截取内容,前包后不包)*/
System.out.println(str.substring(1));//从指定下标截取字符串
System.out.println(str.substring(2,5)+"---0b001");//截取指定下标处的字符串
/*文件重命名*/
String id= UUID.randomUUID().toString();//生成随机ID
System.out.println(id);
String file = "1.2.3.4.jpg";
//截取并生成新的文件名
System.out.println(id+file.substring(file.lastIndexOf('.')));
System.out.println(file.substring(file.lastIndexOf('.')));
System.out.println(file.substring(file.lastIndexOf('4')));
}
}
2.4.4 字符/字符数组相关
- char charAt(index); 返回 [index]位置的字符
- char toCharArray(): 将此字符串转换为一个新的字符数组返回
- static String valueOf(char[] date): 返回指定数组中表示该字符序列的 String
- static String valueOf(char[] date, int offset, int count): 返回同上
- static String copyValueOf(char[] date): 返回同上
- static String copyValueOf(char[] date, int offset, int count): 返回同上
@Test
public void test2(){
String s="每日新冠核酸阳性4000人以上";
/*18. char charAt(index); 返回 [index]位置的字符 */
System.out.println(s.charAt(5));//酸
/*19. char toCharArray(): 将此字符串转换为一个新的字符数组返回*/
char[] chars = s.toCharArray();
System.out.println(chars.getClass());//class [C
/*20. 返回指定数组中表示该字符序列的 String*/
String valueOf = String.valueOf(new char[]{'t', 'o', 'm'});
System.out.println(valueOf);//tom
/*22. static String copyValueOf(char[] date): 返回同上*/
String copyValue = String.copyValueOf(new char[]{'t', 'o', 'm'});
System.out.println(copyValue);//tom
System.out.println(valueOf == copyValue);//false
}
2.4.5 开头和结尾
- boolean startsWith(xx): 测试此字符串是否以 指定前缀xx 开始
- boolean startsWith(String prefix, int toffset): 测试从特定下标处,此字符串是否以 指定前缀开始
- boolean endsWith(xx): 测试此字符串是否以 指定后缀xx 结束
2.4.7 String在内存中的地址和指向
package com.lCommonClasses.fString;
/**
* String在内存中的地址和指向
*/
public class String1 {
public static void main(String[] args) {
/**1 常量池中的引用
1. 栈中开辟一块空间存放的是 引用str1的地址,
2. String池中开辟一块空间,存放String常量"abc",
3. 引用str1指向池中String常量"abc",
4. str1所指代的地址即常量"abc"所在地址 */
String str1 = "abc";
System.out.println(str1 == "abc");// 结果,true
/**2 堆中的引用
1. 栈中开辟一块空间存放的是 引用str2的地址,
2. 堆中开辟一块空间存放一个新建的String对象,内容是"abc",
3. 引用str2指向堆中的新建的String对象"abc",
4. str2所指代的对象地址为堆中地址,而常量"abc"地址在池中 */
String str2 = new String("abc");
System.out.println(str2 == "abc");// 结果,false
/**3 两个堆中的引用
1. 栈中开辟一块空间存放的是 引用str3的地址,
2. 堆中开辟一块新空间存放另外一个(不同于str2所指)新建的String对象,
3. 引用str3指向另外新建的那个String对象
4. str3和str2指向堆中不同的String对象,地址也不相同 */
String str3 = new String("abc");
System.out.println(str3 == str2);// 结果,false
/** 4.编译器有合并已知量的优化功能
1. 栈中开辟一块空间存放引用str4,
2. 根据编译器合并已知量的优化功能,池中开辟一块空间,
存放合并后的String常量"ab",
3. 引用str4指向池中常量"ab",
4. str4所指即池中常量"ab" */
String str4 = "a" + "b";
System.out.println(str4 == "ab");// 结果,true
/** 5.和4相同*/
final String s = "a";
String str5 = s + "b";
System.out.println(str5 == "ab");// 结果,true
/** 6 toString()方法,还原一个新的String对象
1. 栈中开辟一块空间存放引用s1,s1指向池中String常量"a",
2. 栈中开辟一块空间存放引用s2,s2指向池中String常量"b",
3. 栈中开辟一块空间存放引用str6,
4. s1+s2通过 StringBuilder的最后一步toString()方法
还原一个新的String对象"ab",因此堆中开辟一块空间存放此对象,
5. 引用str6指向堆中(s1 + s2)所还原的新String对象,
6. str6指向的对象在堆中,而常量"ab"在池中 */
String s1 = "a";
String s2 = "b";
String str6 = s1 + s2;
System.out.println(str6 == "ab");// 结果,false
/**7 编译器有合并已知量的优化功能
1.栈中开辟一块空间存放引用str7,
2.toUpperCase()方法还原一个新的String对象"ABC",
池中并未开辟新的空间存放String常量"ABC",
3.引用str7指向堆中的新String对象
*/
String str7 = "abc".toUpperCase();
String str8 = "abc".toLowerCase();
System.out.println(str7 == "ABC");// 结果,false
System.out.println(str8 == "abc");// 结果,true
}
}
3.StringBuffer、StringBuilder
3.1 String/StringBuffer/StringBuilder区别:
String:不可变的字符序列,拼接时需要创建新对象后再拼接
StringBuffer:JDK1.0提供的类,可变的字符序列,不需要创建新对象-可变长,线程安全(synchronized修饰方法),做线程同步检查,效率较低;
StringBuilder:JDK1.5提供的类,可变的字符序列,不需要创建新对象-可变长,线程不安全,不做线程同步检查,因此效率较高,建议使用
3.2 String、StringBuffer、StringBuilder
3.2.1 String、StringBuffer、StringBuilder底层
- String :(jdk1.8及之前:)底层使用char[];(jdk1.9之后:)底层使用byte[]
- StringBuffer(jdk1.8及之前:)底层使用char[];(jdk1.9之后:)底层使用byte[]
- StringBuilder (jdk1.8及之前:)底层使用char[];(jdk1.9之后:)底层使用byte[]
2)StringBuffer和 StringBuilder方法一样,参考
StringBuilder
的代码
3.2.2 String、StringBuilder源码分析
- String:源码分析
String s1 = new String();// char[] value = new char[0];
String:String s2 = new String(“abc”);// char[] value = new char[]{‘a’,‘b’,‘c’};- StringBuilder:可变性源码分析(继承了
AbstractStringBuilder
,其属性有)
char[] value;// 存储字符序列(没有用 final修饰)
int count;// 实际存储字符的个数(没有用 final修饰)
StringBuilder s1 = new StringBuilder(); // char[] value = new char[16];
StringBuilder s2 = new StringBuilder("abc"); // char[] value = new char[16 + "abc".length];
s1.append("ac"); // value[0] = 'a'; value[1] = 'c';
s1.append("b"); // value[2] = 'b';
3.2.3 AbstractStringBuilder扩容机制
StringBuffer和 StringBuilder都继承了 AbstractStringBuilder抽象类。
不断添加字符时,一旦超过 value.length时,就需要扩容。
默认扩容为原有容量的2倍+ 2
,并将原有 value数组中的元素复制到新的 char数组中。
特殊情况:原来容量的 2倍+ 2,还不满足时,需要多大容量就新建多大容量的数组
3.2.4 源码启示
- 开发中经常对字符进行字符串的增、删、改操作,建议使用StringBuffer 或StringBuilder替换String,因为String效率低,进行这些操作时 它的空间占用率较高;
- 开发中,不涉及线程安全问题时,建议使用 StringBuilder 替换StringBuffer。因为StringBuilder效率更高;
- 开发中若可以大体确定要操作的字符个数,建议使用带
int capacity
参数的 构造器。因为可以避免底层多次扩容操作,性能会更好。
3.3 StringBuilder常用方法:
1)增:
- StringBuilder append(xx);// 提供了很多 append()方法,用于进行字符串追加的拼接方式
- StringBuilder insert(int index, xx);// 在[ index] 处插入字符 xx
2)删:
- StringBuilder delete(int start, int end);// 删除[ start, end)之间的字符
- StringBuilder deleteCharAt(int index);// 删除[ index] 处的字符
3)改:
- void setCharAt(int index, char c);// 用 c替换[ index] 处的字符
- StringBuilder replace(int start, int end, String str);// 用 str替换[ start, end) 范围内的字符
4)查:
- char charAt(int index);查找[ index] 处的字符
- StringBuilder reverse();将数组反转
- int length() : 返回实际存储字符数据的长度
package com.kString;
public class StringBuilder1 {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder("builder");
System.out.println(builder);
/** 一.字符串的增加 */
// 1.拼接字符串,并返回自身
System.out.println(builder.append("-hello"));
// 2.添加字符串(在指定位置)
System.out.println(builder.insert(5, " String "));
// 3.拼接文件内容
System.out.println(builder.append(new Text()));
/** 二.字符串的删除 */
// 4.删除这个区间里的字符串
System.out.println(builder.delete(20,30));
// 5.删除指定的字符
builder.deleteCharAt(1);
System.out.println(builder);
/** 三.字符串的更新 */
// 6.替换某个字符
builder.setCharAt(1,'神');
System.out.println(builder);
// 7.替换指定位置的字符串
System.out.println(builder.replace(20,30,"~-S吧-~"));
/** 四.字符串的查询 */
// 8.获取指定字符
System.out.println(builder.charAt(1));
// 9.反转字符串:从后往前读取原来的字符串
System.out.println(builder.reverse());
/** 五.String 和StringBuilder 的相互转换*/
// 10.StringBuilder转String
String str = builder.toString();
System.out.println(str);
// 11.String转StringBuilder
StringBuilder buil = new StringBuilder(str);
System.out.println(buil);
}
}
class Text{
StringBuilder builder = new StringBuilder(
"Text:StringBuilder -可变长,JDK1.5提供的类," +
"线程不安全,不做线程同步检查,因此效率较高,建议使用");
Text(){
System.out.println(builder);
}
}
- 方法链的调用
- length()
- setLength(int newLength);
package com.kString;
import org.junit.Test;
public class StringBuilderPro1 {
@Test
public void test1() {
StringBuilder builder = new StringBuilder();
// 1.方法链的调用
builder.append("abc").append("123").append("@#$");
System.out.println(builder);//abc123@#$
// 2.length();实际存储字符的个数
System.out.println(builder.length());// 结果,9
// 3.setLength(int newLength);将数组的 count值设置为 3,
builder.setLength(3);
System.out.println(builder);// 结果,abc
builder.setLength(6);
System.out.println("\""+builder+"\"");// 结果,"abc "
//设置的长度 6>3,后面的值已经用 0填充了
System.out.println(builder.charAt(5) == 0);//true
builder.append("D");
System.out.println("\""+builder+"\"");//"abc D"
}
}
3.4 其他 API
- int indexOf(String str): 在当前字符序列中查询 str第一次出现的下标;
- int indexOf(String str, int fromIndex): 在当前字符序列中,从[fromIndex,之后]查询 str第一次出现的下标;
- int lastIndexOf(String str): 在当前字符序列中查询 str最后一次出现的下标;
- int lastIndexOf(String str, int fromIndex): 在当前字符序列中,从[fromIndex,之前]查询 str最后一次出现的下标;
- String substring(int start): 从[ start] 截取当前字符序列,到最后
- String substring(int start, int end): 截取[ start, end] 范围内的字符序列
- String toString(): 返回此序列中数据的字符串表示形式;
- void setLength(int newLength): 设置当前字符序列长度为 newLength .
### 6.3 字符拼接
```java
package com.kString;
public class StringBuilder3 {
public static void main(String[] args) {
String str1 = "a" + 1 + 2;//a12
int str2 = 'a'+ 1 + 2;//整数:100,对'a'进行转义
String str3 = 1 + 2 + "a" ;//3a
//Str1:a12; str2:100; str3:3a
System.out.println("Str1:"+str1+"; str2:"+str2+"; str3:"+str3);
String t="t";
String o="o";
String m="m";
String str= "tom";
//字符串拼接如果都是常量那么就和直接写一个整体效果是一样的
String str4="a"+"b"+"c";//创建了1个对象String
String str5=t+o+m;//创建了1个对象StringBuilder
System.out.println(str5);//tom
System.out.println(str==str4);//true
System.out.println(str4==str5);//false
/*`String str5=t+o+m;`在虚拟机优化时会变成,
只要含有 连续的字符串拼接 就会这样优化*/
StringBuilder builder = new StringBuilder();
builder.append(t);
builder.append(o);
builder.append(m);
String str6= builder.toString();
System.out.println(str6);//tom
}
}
3.5 三者效率比较
测试String、StringBuilder、StringBuilder的执行速度
package com.kString;
public class TestSpeed {
public static void main(String[] args) {
String s1= "";
StringBuffer s2 = new StringBuffer();//JDK1.0
StringBuilder s3 = new StringBuilder();//JDK1.5
/**1.测试 String用时,十万次*/
long firstTime = System.currentTimeMillis();//获取系统时间
for (int i = 0; i < 10_0000; i++) {
s1=s1.concat("拼接");
}
long lastTime = System.currentTimeMillis();
System.out.println("String用时:"+(lastTime-firstTime));
/**2.测试 StringBuffer用时,一千万次*/
firstTime = System.currentTimeMillis();
for (int i = 0; i < 1000_0000; i++) {
s2.append("拼接");
}
lastTime = System.currentTimeMillis();
System.out.println("JDK1.0_StringBuffer用时:"+(lastTime-firstTime));
/**3.测试 StringBuilder用时,一千万次*/
firstTime = System.currentTimeMillis();
for (int i = 0; i < 1000_0000; i++) {
s3.append("拼接");
}
lastTime = System.currentTimeMillis();
System.out.println("JDK1.5_StringBuilder用时:"+(lastTime-firstTime));
}
}
/*
String用时:4074
JDK1.0_StringBuffer用时:277
JDK1.5_StringBuilder用时:144
*/
3.6 Interview 面试:地址指向问题
3.6.1 问题一:StringBuffer地址指向问题
package com.kString;
/**
* 场景一:StringBuffer对象,地址指向问题
*/
public class StringInterview1 {
public static void main(String[] args) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
operate(a, b);
System.out.println(a+", "+b);// ABxy, B
}
private static void operate(StringBuffer x, StringBuffer y) {
x.append(y);
// 将 x指向的地址给了 y
y=x;// 此时 x和 y都指向了 x的地址
y.append('x');
x.append("y");
}
}
3.6.2 问题二:String、StringBuffer地址传递问题
package com.kString;
public class StringInterview2 {
// Replace:取代
public static void main(String[] args) {
String testString = new String("java");
StringBuffer testBuffer = new StringBuffer("java");
stringReplace(testString);
bufferReplace(testBuffer);
System.out.println(testString);// 结果,java
System.out.println(testBuffer);// 结果,javaC
}
public static void stringReplace(String text){
// 对参数进行了替换操作,底层新建了一个对象
text=text.replace('j','i');
// 形参的值改变了,但是改变前后是两个对象,地址也不一样
System.out.println("textString: "+text);// 结果,textString: iava
}
public static void bufferReplace(StringBuffer text){
// 这是对同一个对象进行的操作
text.append("C");
// 新建了一个对象,地址空间和之前的不是一个
text = new StringBuffer("hello");
text.append("World!");
System.out.println("testBuffer: "+text);// 结果,testBuffer: helloWorld!
}
}
3.6.3 问题三:String、StringBuffer地址指向问题
package com.kString;
public class StringInterview3 {
public static void main(String[] args) {
String s = "bbbb";
StringBuffer buff = new StringBuffer("bbbb");
change(s, buff);
System.out.println(s + "\"" + buff + "\"");// 结果,bbbb"aaaa"
}
private static void change(String s, StringBuffer buff) {
s = "aaaa";
/*这里没有新建StringBuffer,所以这里对 buff的操作
都是直接对引用地址里内容 的操作*/
buff.setLength(0);
System.out.println("\"" + buff + "\"");// 结果,""
buff.append("aaaa");
System.out.println(buff.length());// 结果,4
System.out.println("\"" + buff + "\"");// 结果,"aaaa"
}
}
3.6.4 问题四:null的问题
String、StringBuffer关于 null的问题
package com.kString;
public class StringInterview4 {
public static void main(String[] args) {
String str = null;
StringBuffer buff = new StringBuffer();
// append方法底层存放了一个字符串 null
buff.append(str);
System.out.println(buff.length());// 结果,4
System.out.println("-" + buff);// 结果,-null
StringBuffer buff1 = new StringBuffer(str);
System.out.println("--" + buff1);// 结果,空指针异常
}
}