String类、StringBuilder类、StringBuffer类是三个字符串相关类,String类对象是不可变字符序列,后两者是可变字符序列,StringBuilder效率高但线程不安全,StringBuffer效率低但线程安全。
String
String类对象是不可变字符序列,位于java.lang包中。
在String类中存放字符串的数组是由final修饰的常量,说明其只可以被初始化一次,赋值之后再对该String对象进行操作(截取替换等),得到的都是一个新的字符串,原字符串不会发生改变。
创建String对象
字面量创建String对象
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
堆内存中有一个单独的部分叫做字符串常量池。
执行String s1 = “hello”;时,常量池中添加"hello"对象,返回引用地址给s1对象
执行String s2 = “hello”;时,通过equals()方法判断常量池中是否已有值为"hello"的对象,若有,直接返回引用地址。
可以看出这样的机制下,s1和s2指向相同,所以执行"==" 和equals,结果都是true。
new创建String对象
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s3 == s4); //false
System.out.println(s3.equals(s4)); //true
执行String s3 = new String(“abc”);时,在常量池中添加"abc"对象,在堆中创建值为"abc"的对象s3,返回指向堆中s3的引用。
执行String s4 = new String(“abc”);时,常量池中已经有值为"abc"的对象,不做处理,在堆中创建值为"abc"的对象s4,返回指向堆中s4的引用。
可以看出s3和s4引用的是不同的对象,所以==判断为false,String类型是equals方法特例,所以结果是true。
综上,用字面量创建字符串对象时,只在常量池创建对象,用new的时候,在常量池和堆内存中都创建了对象,所以字面量方式更加省内存。
字面量方式相加
String s5 = “x” + “y”;
执行时,经过JVM优化,直接在常量池中添加"xy"对象。
但是JVM只可以判断字符串常量,如果是变量相加则无法优化。
String str1 = "hello" + "world";
String str2 = "helloworld";
String str3 = "hello";
String str4 = "world";
String str5 = str3 + str4;
System.out.println(str1 == str2); //true
System.out.println(str2 == str5); //false
new方式相加
String s6 = new String(“1”) + new String(“1”) + new String(“2”);
通过StringBuilder实现,先在常量池中添加"1"对象,第二个1时因为常量池中存在所以不操作,再在常量池中添加"2"对象,然后在堆中创建值为"112"的对象,给s6返回指向堆中"112"对象的引用。
String类常见方法
方法 | |
---|---|
char charAt(int index) | 返回字符串中第index位置的字符,范围0~length-1 |
boolean equals(String other) | 如果字符串和other相等,返回true,否则返回false |
boolean equalsIgnoreCase(String other) | 忽略英文大小写判断字符串是否相等 |
int indexOf(String str) | 返回从头开始查找子字符串str在字符串在的索引,没有则返回-1 |
int lastIndexOf(String str) | 返回从末尾开始查找子字符串str在字符串在的索引,没有则返回-1 |
int length() | 返回字符串长度 |
String replace(char oldChar,char newChar) | 返回一个由newChar替换oldChar的新字符串 |
boolean startsWith(String prefix) | 如果字符串以prefix开始,返回true |
boolean endsWith(String prefix) | 如果字符串以prefix结束,返回true |
String subString(int beginIndex) | 返回一个新字符串,截取从字符串beginIndex到字符串结尾 |
String subString(int beginIndex,int endIndex) | 返回一个新字符串,截取从字符串beginIndex到endIndex-1 |
String toLowerCase() | 返回一个新字符串,将原字符串所有大写字母改成小写 |
String toUpperCase() | 返回一个新字符串,将原字符串所有小写字母改成大写 |
String trim() | 删除字符串中头部和尾部的空格 |
String str = "123strabcdstrXYZ";
//charAt
System.out.println(str.charAt(2)); //3
System.out.println(str.charAt(str.length()-1)); //Z
//indexOf
System.out.println(str.indexOf("str")); //3
System.out.println(str.lastIndexOf("str")); //10
System.out.println(str.indexOf("df")); //-1
//replace
System.out.println(str.replace('r', 'u')); //123stuabcdstuXYZ
System.out.println(str.replace("str", "HELLO")); //123HELLOabcdHELLOXYZ
//startsWith、endsWith
System.out.println(str.startsWith("23")); //false
System.out.println(str.endsWith("YZ")); //true
//subString
System.out.println(str.substring(5)); //rabcdstrXYZ
System.out.println(str.substring(5, 10)); //rabcd
//toUpperCase、toLowerCase
System.out.println(str.toUpperCase()); //123STRABCDSTRXYZ
System.out.println(str.toLowerCase()); //123strabcdstrxyz
//trim
String str1 = "\ta bc ";
System.out.println("*" + str1 + "*"); //* a bc *
System.out.println("*" + str1.trim() + "*"); //*a bc*
StringBuilder和StringBuffer
StringBuilder和StringBuffer都是可变字符序列,保存其对象的数组是变量。
一般不涉及线程问题时,建议采用StringBuilder,因为它的效率更高。
StringBuilder和StringBuffer常用方法
StringBuilder和StringBuffer都继承了抽象类AbstractStringBuilder,所以常用方法都相同。
方法 | 作用 |
---|---|
StringBuilder append() | 在StringBuilder对象末尾添加字符串序列,返回自身对象 |
StringBuilder delete(int start, int end) | 删除StringBuilder对象从索引start到end-1的字符串,返回自身对象 |
StringBuilder deleteCharAt(int index) | 删除StringBuilder对象索引为index的的字符,返回自身对象 |
StringBuilder insert() | 在StringBuilder对象指定位置插入字符串序列,返回自身对象 |
StringBuilder reverse() | 用于逆序排列字符串,返回自身对象 |
String toString() | 将StringBuilder对象中的数据以String类表示 |
此外,还有String类类似的方法:indexOf、subString、length、charAt等。
StringBuilder sb1 = new StringBuilder();
//在StringBuilder对象末尾添加字符串序列
sb1.append(123);
System.out.println(sb1); //123
//因为返回其本身,所以可以连续调用
sb1.append("a").append("b").append("c");
System.out.println(sb1); //123abc
//插入
sb1.insert(0, "va").insert(0, "ja");
System.out.println(sb1); //java123abc
//删除
sb1.deleteCharAt(4).deleteCharAt(5);
System.out.println(sb1); //java2abc
sb1.deleteCharAt(4).delete(0, 4);
System.out.println(sb1); //abc
//逆序
System.out.println(sb1.reverse()); //cba
String使用陷阱
我们知道了String是不可变字符串
所以当我们执行String s = “a”;s = s + “b”;时,s指向了新字符串对象"ab",而原来的字符串对象"a"就被废弃了,如果多次进行这样的操作,就会导致大量字符串对象留存在内存中,降低效率。
//进行大量字符串拼接时
//使用String类拼接
String str11 = "";
long num1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time1 = System.currentTimeMillis();//获取系统当前时间
for(int i = 0; i < 5000; i++) {
str11 = str11 + i; //相当于产生了5000个对象
}
long num2 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time2 = System.currentTimeMillis();//获取系统当前时间
System.out.println("String占用内存:" + (num1 - num2));
System.out.println("String占用时间:" + (time2 - time1));
//使用StringBuilder类拼接
StringBuilder sb11 = new StringBuilder();
long num3 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time3 = System.currentTimeMillis();//获取系统当前时间
for(int i = 0; i < 5000; i++) {
sb11.append(i);
}
long num4 = Runtime.getRuntime().freeMemory();//获取系统剩余内存空间
long time4 = System.currentTimeMillis();//获取系统当前时间
System.out.println("StringBuilder占用内存:" + (num3 - num4));
System.out.println("StringBuilder占用时间:" + (time4 - time3));
/* 运行结果
* String占用内存:4086616
* String占用时间:51
* StringBuilder占用内存:524288
* StringBuilder占用时间:0
*/
所以,在做大量字符串修改时,要避免使用String类。