目录
1. String
1.1 java.lang.String 类是不可变类
-
String表示字符串类型,属于引用数据类型,不属于基本数据类型。
-
在java中随便使用双引号括起来的都是String对象。例如:“abc”,“def”,“hello world!”,这是3个String对象。
-
java中规定,双引号括起来的字符串,是不可变的,也就是说 “abc” 自出生到最终死亡,不可变,不能变成"abcd",也不能变成"ab"。
-
在JDK当中双引号括起来的字符串,例如:“abc” “def” 都是直接存储在“方法区"的"字符串常量池"当中的。
-
为什么SUN公司把字符串存储在一个“字符串常量池”当中呢。因为字符串在实际的开发中使用太频繁。为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
注意:只要采用双引号赋值字符串,那么在编译期将会放到方法区中的字符串的常量池里,如果是运行时对字符串相加或相减会放到堆中(放之前会先验证方法区中是否含有相同的字符串常量,如果存在,把地址返回,如果不存在,先将字符串常量放到池中,然后再返回该对象的地址)。
示例代码:
public class StringTest01 {
public static void main(String[] args) {
// 这两行代码表示底层创建了3个字符串对象,都在字符串常量池当中。
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
// 凡是双引号括起来的都在字符串常量池中有一份。
// new对象的时候一定在堆内存当中开辟空间。
String s3 = new String("xy");
// i变量中保存的是100这个值。
int i = 100;
// s变量中保存的是字符串对象的内存地址。
// s引用中保存的不是"abc",是0x1111。
// 而0x1111是"abc"字符串对象在“字符串常量池”当中的内存地址。
String s = "abc";
}
}
内存分析:
1.2 String s1 = “xxx” 和 String s2 = new String(“xxx”)
示例代码:
public class StringTest02 {
public static void main(String[] args) {
String s1 = "xyz"; // "hello"是存储在方法区的字符串常量池当中
String s2 = "xyz"; // 这个"hello"不会新建。(因为已经存在了!)
System.out.println("s1 = s2: " + (s1 == s2)); // true,双等号比较的是变量中保存的内存地址
String x = new String("xyz");
String y = new String("xyz");
System.out.println("x = y: " + (x == y)); // false,双等号比较的是变量中保存的内存地址
System.out.println("x equals y: " + (x.equals(y))); // true,字符串对象之间的比较不能使用“==”,应该调用String类的equals方法。
System.out.println("s1 = x: " + (s1 == x)); // false
}
}
运行结果:
内存分析:
-
如果是采用双引号引起来的字符串常量,首先会到常量池中去查找,如果存在就不再分配,如果不存在就分配,常量池中的数据是在编译期赋值的,也就是生成 class 文件时就把它放到常量池里了,所以 s1 和 s2 都指向常量池中的同一个字符串 “xyz”。
-
关于 x,y 采用的是 new 的方式,在 new 的时候存在双引号,所以他会到常量区中查找 “xyz”,而常量区中存在 “xyz”,所以常量区中将不再放置字符串,而 new 关键子会在堆中分配内存,所以在堆中会创建对象 xyz。
-
如果比较 x 和 y 必须采用 equals(),String 已经对 eqauls 方法进行了覆盖。
1.3 两个 String 面试题
①下面代码创建了几个对象?
String s1 = new String("hello") ;
String s2 = new String("hello") ;
3个,堆中2个,方法区1个。所以使用 String 时,不建议使用 new 关键字,因为使用 new 可能会创建多个对象。
②String 为什么是不可变的?
String 类底层是一个byte[]数组,数组一旦创建长度不可变。而且这个数组被 final 修饰,被 final 修饰的引用一旦指向某个对象之后,不可再指向其它对象,所以 String 是不可变的!
1.4 String 常用方法
具体使用方法请参考帮助文档。
方法名 | 描述 |
---|---|
length() | 取得字符串的长度 |
isEmpty() | 判断字符串是否为空串 |
toCharArray() | 将字符串转换为字符数组 |
endsWith() | 判断字符串是否以指定的后缀结束 |
startsWith() | 判断字符串是否以指定的前缀开始 |
equals() | 字符串相等比较,不忽略大小写 |
equalsIgnoreCase() | 字符串相等比较,忽略大小写 |
indexOf() | 取得指定字符在字符串的位置 |
lastIndexOf() | 返回最后一次字符串出现的位置 |
replace() | 用新内容替换字符串中指定的内容 |
split() | 根据指定的表达式拆分字符串 |
substring() | 截子串 |
trim() | 去前后空格 |
valueOf() | 将其他类型转换成字符串 |
charAt() | 返回指定索引处的字符 |
compareTo() | 按字典顺序比较字符串 |
contains() | 判断是否包含某字符串 |
toLowerCase() | 将字符串所有字符转换为小写 |
toUpperCase() | 将字符串所有字符转换为大写 |
2. StringBuffer 和 StringBuilder
因为 String 是不可变对象,如果多个字符串进行拼接,将会形成多个对象,这样可能会造成内存溢出,会给垃圾回收带来工作量,所以频繁拼接字符串最好不要用 String。如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:java.lang.StringBuffer、java.lang.StringBuilder
2.1 StringBuffer
StringBuffer 称为字符串缓冲区,它的工作原理是:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列。StringBuffer 是可变对象,这个是 String 最大的不同。
示例代码:
public class StringBufferTest01 {
public static void main(String[] args) {
// 创建一个初始化容量为16个byte[] 数组(字符串缓冲区对象)
StringBuffer sb1= new StringBuffer();
// 拼接字符串,以后拼接字符串统一调用 append()方法
sb1.append("a");
sb1.append("b");
sb1.append("d");
sb1.append(3.14);
sb1.append(true);
// append方法底层在进行追加的时候,如果byte数组满了,会自动扩容。
sb1.append(100L);
System.out.println(sb1.toString());
// 指定初始化容量的StringBuffer对象(字符串缓冲区对象)
// 指定的大一点,这样可以减少底层的扩容次数,提高效率
StringBuffer sb2 = new StringBuffer(100);
sb2.append("hello");
sb2.append("world");
sb2.append("hello");
sb2.append("kitty");
System.out.println(sb2);
}
}
运行结果:
2.2 StringBuilder
用法同 StringBuffer,但StringBuilder 和 StringBuffer 的区别是:
StringBuffer 中所有的方法都是同步的,是线程安全的,但速度慢;
StringBuilder 的速度快,但不是线程安全的。
2.3 一个面试题
StringBuilder / StringBuffer为什么是可变的呢?
StringBuffer / StringBuilder 底层实际上是 byte[] 数组,这个数组没有被final修饰,StringBuffer / StringBuilder 的初始化容量我记得应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方System.arraycopy()…是这样扩容的。所以 StringBuilder / StringBuffer 适合于使用字符串的频繁拼接操作。