1. String 类
不可变的字符序列,底层用char[] 存储。
1.1 类介绍
package java.lang;
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
...
}
1)String 类被final 修饰,不能被继承;
2)String 类实现Serializable 接口,表示字符串支持序列化;
3)String 类实现Comparable 接口,表示字符串可以比较大小;
1.2 不可变性
private final char value[];
字符串底层使用char[] 存储数据,用final 所修饰,说明数组内容不可被修改,即String 是不可变的字符序列。
1.2.1 字面量赋值
String s1 = "JavaEE";
通过字面量的方式(不同于new)给一个字符串赋值,此时的字符串值声明在方法区的字符串常量池中,栈中存放的是字符串在常量池中的地址;
<创建一个对象:字符串常量池中>
栈(常量池地址) ⇒ 字符串常量池(字符串值)
注意:字符串常量池中不会存放两个相同内容的字符串,无则新建,有则复用;
1.2.2 new 方式赋值
String s3 = new String("JavaEE");
通过new 在堆内存中开辟内存空间,栈中存放该对象的堆内存地址,而真正的字符串值仍旧存储在字符串常量池中,其实堆中的内存空间存放的是字符串常量池的地址!
<创建两个对象:堆和字符串常量池创建对象>
栈(堆地址) ⇒ 堆(常量池地址) ⇒ 字符串常量池(字符串值)
1.3 字符串拼接
1.3.1 字面量拼接
String s1 = "abc";
String s2 = "def";
String s3 = "abcdef";
String s4 = "abc"+"def";
System.out.println(s3 == s4); // true
字面量拼接,值存放在字符串常量池中,相同值复用,s3与s4 地址相同,输出true
<创建一个对象:字符串常量池中>
栈(常量池地址) ⇒ 字符串常量池(字符串值)
1.3.2 变量名参与的拼接
String s1 = "abc";
String s2 = "def";
String s3 = "abcdef";
String s5 = s1 + "def";
String s6 = s1 + s2;
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
变量名参与的拼接,相当于new ,需要在堆中开辟内存空间,存放字符串常量池中的地址,值仍旧存放在字符串常量池中
<创建两个对象:堆和字符串常量池创建对象>
栈(堆地址) ⇒ 堆(常量池地址) ⇒ 字符串常量池(字符串值)
1.3.3 常量名参与拼接
final String s1 = "abc";
String s2 = "def";
String s3 = "abcdef";
String s5 = s1 + "def";
String s6 = s1 + s2;
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // false
只有变量名参与拼接,才相当于相当于new,常量拼接与字面量拼接相同,只在常量池中创建对象
<创建一个对象:字符串常量池中>
栈(常量池地址) ⇒ 字符串常量池(字符串值)
1.3.4 intern()
String s1 = "abc";
String s1 = "abc";
String s2 = "def";
String s3 = "abcdef";
String s6 = s1 + s2;
String s7 = s6.intern();
System.out.println(s3 == s7); // true
s6.intern() 返回的s7只在常量池中声明,所以为true
1.4 常用方法
public class StringMethodTest {
@Test
public void test1(){
String str = "HelloWorld";
System.out.println(str.length());
System.out.println(str.charAt(0)); // 根据索引取值
System.out.println(str.charAt(9));
System.out.println(str.isEmpty());
System.out.println(str.toLowerCase()); // 转小写
System.out.println(str.toUpperCase()); // 转大写
String str1 = " a b c ";
System.out.println(str1.trim()); // 去除首尾空格
System.out.println(str.equals(str1)); // 比较内容
System.out.println(str.equalsIgnoreCase(str1)); // 比较内容(忽略大小写)
System.out.println(str.concat("Java")); // 拼接
System.out.println("a".compareTo("b")); // 比较大小(可用于字符串排序)
System.out.println(str.substring(5));
System.out.println(str.substring(0,5));
System.out.println(str.startsWith("H"));
System.out.println(str.startsWith("e",1));
System.out.println(str.endsWith("ld"));
System.out.println(str.contains("ll"));
System.out.println(str.indexOf("Hello")); // 找索引
System.out.println(str.indexOf("llo",5)); // 找索引(指定位置开始)
System.out.println(str.lastIndexOf("llo")); // 从后往前找索引
System.out.println(str.indexOf("llo",6));
System.out.println(str.replace("e","o")); // 替换字符
System.out.println(str.replace("ll","ooo")); // 替换字符串
System.out.println("a5b5c5d5".replaceAll("\\d+","-")); // 正则
System.out.println("a5b5c5d5".replaceFirst("\\d+","-")); // 正则(只替换第一个)
System.out.println("123456".matches("\\d+")); // 正则匹配
System.out.println("a|b|c".split("|")); // 切割字符串,返回数组
}
}
1.5 类型转换
1.5.1 与基本类型、包裹类转换
@Test
public void test1(){
// 1. String 转基本类型:包装类.parseXxx(str)
String str1 = "123";
int num = Integer.parseInt(str1);
// 2. 基本数据类型、包装类转String,String.valueOf(xxx)
String str2 = String.valueOf(num);
String str3 = num + "";
}
1.5.2 与char[] 转换
@Test
public void test1(){
// 1. String 转char[]
char[] chars = str1.toCharArray();
for (int i = 0; i < chars.length; i++) {
System.out.println(chars[i]);
}
// 2.1 char[] 转String
char[] arr = new char[]{'j','a','v','a'};
String str4 = new String(arr);
System.out.println(str4);
// 2.2 char[] 转String,指定长度
String str5 = new String(arr,0,2);
System.out.println(str5);
}
1.5.3 与byte[] 转换
@Test
public void test2() throws UnsupportedEncodingException {
// 1.字符串转字节(编码) getBytes()
String str = "abc中国";
byte[] bytes = str.getBytes(); // 默认字符集
System.out.println(Arrays.toString(bytes)); // UTF-8 一个汉字三个字节
byte[] gbks = str.getBytes("gbk");
System.out.println(Arrays.toString(gbks)); // gbk 一个汉字两个字节
// 1.2 字节转字符串(解码) String 构造器
String str2 = new String(bytes);
System.out.println(str2);
String str3 = new String(gbks); // 字符集不一致会乱码
System.out.println(str3);
String str4 = new String(gbks,"gbk"); // 字符集保持一致
System.out.println(str4);
}
2. StringBuffer 类
可变的字符序列,线程安全的(synchronized),效率偏低
多个线程操作共享数据的时候,该共享数据用需要用StringBuffer修饰;
2.1 类介绍
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
...
}
1)String 类被final 修饰,不能被继承;
2)String 类实现Serializable 接口,表示字符串支持序列化;
2.2 构造器
2.2.1 无参构造器
StringBuffer buffer = new StringBuffer();
StringBuffer :
public StringBuffer() {
super(16);
}
super(16):
char[] value;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
底层创建一个16位的字符数组:char[] value = new char[16]
2.2.2 有参构造器
StringBuffer buffer2 = new StringBuffer("abc");
StringBuffer :
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
创建一个在"abc" 长度基础上+16 的字符数组
2.3 扩容底层数组
利用构造器初始化了底层数组的长度,但值不断增加,长度超过初始长度时,如何扩大容量?
// 初始化16位字符数组
StringBuffer buffer1 = new StringBuffer();
// 追加20位
buffer1.append("12345678901234567890");
append:
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
super.append(str):
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
ensureCapacityInternal(count + len):
当追加内容长度 + 原有值长度 > 数组长度时,根据总长度新建数组,将新增内容及原有内容一同复制到新数组中,实现扩容;
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
newCapacity(minimumCapacity):
新数组的长度为总长度 X 2 + 2
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
相较于String 每次都要新开辟内存空间再存值来说,效率更高;
2.4 指定底层数组长度
由于扩容的过程,仍然需要进行新内存空间的开辟,并且要对数组内容进行复制,所以可以提前指定底层数组长度,从而减少扩容,提升效率
StringBuffer buffer3 = new StringBuffer(100);
2.5 常用方法
@Test
public void test1(){
StringBuffer buffer1 = new StringBuffer("abc");
buffer1.append("123");
System.out.println(buffer1);
buffer1.delete(2,4);
System.out.println(buffer1);
buffer1.replace(0,2,"Hello");
System.out.println(buffer1);
buffer1.insert(5,false);
System.out.println(buffer1);
System.out.println(buffer1.reverse()); // 反转
buffer1.append("123").append("456").append("789"); // 方法链写法
System.out.println(buffer1.toString());
}
输出:
abc123
ab23
Hello23
Hellofalse23
32eslafolleH
32eslafolleH123456789
3. StringBuilder 类(JDK5.0)
StringBuilder 类与StringBuffer 类都是继承与同一个类(AbstractStringBuilder),功能相同,但不同点在于:
没用synchronized 修饰方法,线程不安全,但效率高,所以在不涉及线程安全问题,推荐使用StringBuilder ;
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
...
}
4. 三者效率比较
String 、StringBuffer 、StringBuilder 执行效率比较
public void test2(){
long startTime = 0;
long endTime = 0;
String str = "";
StringBuffer buffer = new StringBuffer();
StringBuilder builder = new StringBuilder();
// 字符串
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
str = str + i;
}
endTime = System.currentTimeMillis();
System.out.println("String 执行时间:" + (endTime - startTime));
// StringBuffer
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
buffer.append(i);
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer 执行时间:" + (endTime - startTime));
// StringBuilder
startTime = System.currentTimeMillis();
for (int i = 0; i < 20000; i++) {
builder.append(i);
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder 执行时间:" + (endTime - startTime));
}
输出:
String 执行时间:2009
StringBuffer 执行时间:3
StringBuilder 执行时间:1