悲伤于过去;
意淫于将来;
遗忘于现在。
目录:
1 String
1.1 定义
- String 表示字符串,使用一对
" "
引起来表示,Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。 - String 是一个 final 类,一方面表示其是不可继承的;另一方面代表不可变的字符序列,String 具有增、删、改方法,但是使用这些方法产生的对象是一个新的对象,而非原对象本身。
- String 的底层是用
char[]
数组来实现的。(事实上,大部分数据结构的底层都是利用数组或者链表实现) - String 实现了 Serializable 接口,这表示字符串是支持序列化的,这与 IO 流的传输密切相关;同时也实现了 Comparable 接口,这表示 String 是可以比较大小的。
1.2 两种实例化方式的差异
String 是一个比较特殊的类,它的实例化不仅仅可以通过 new
的形式,还可以通过字面量定义的方式来实例化。
1.2.1 通过字面量定义的方式来实例化
@Test
public void test(){
//字面量的定义方式:此时的s1和s2的数据声明在JVM方法区中的字符串常量池中
String s1 = "sharm";
String s2 = "luma";
//比较s1和s2的地址值
System.out.println(s1 == s2); // false
s1 = "luma";
System.out.println(s1 == s2); // true
}
方法区的字符串常量池中是不会存储相同内容的字符串的,如下图所示,让为 s1 赋值 luma 时,只是让 s1 重新指向了新的地址(luma 的地址),而非重新创建了一个地址。
1.2.2 通过 new + 构造器的方式来实例化
@Test
public void test(){
//通过new + 构造器的方式:此时的s1和s2保存的是数据在堆空间中开辟空间以后对应的地址值,然后堆空间中保存的地址值是常量池中的地址值
String s1 = new String("sharm");
String s2 = new String("sharm");
String s3 = "sharm";
//比较s1和s2的地址值
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // false
}
1.2.3 案例
@Test
public void test(){
String str1 = new String();
str1 = "zzm";
String str2 = new String("zzm");
String str3 = "zzm";
System.out.println(str1 == str3); // true
System.out.println(str2 == str3); // false
}
以上的内存结构参照的是 hotspot 的 JVM 的内存管理,不同版本的 JVM 有所区别。
1.3 字符串的拼接
/*
1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量;
2. 只要其中有一个是变量,结果就在堆中;
3. 如果拼接的结果调用intern()方法,返回值就在常量池中.
*/
@Test
public void test(){
String s1 = "hellosharm";
String s2 = "sharm";
String s3 = "hello" + s2;
System.out.println(s1 == s3); //false
String s4 = "hello" + "sharm";
System.out.println(s1 == s4); //true
//s5 代表常量
final String s5 = "hello";
String s6 = s5 + "sharm";
System.out.println(s1 == s6);//true
String s7 = s3.intern();
System.out.println(s1 == s7);//true
}
1.4 String 类与其他结构之间的转换
1.4.1 String 与包装类之间的转换
/*
String 与基本数据类型、包装类之间的转换:基本数据类型一定要调用包装类来转换。
String --> 基本数据类型、包装类: 调用包装类的静态方法:parseXxx(str)
基本数据类型、包装类 --> String: 调用String重载的valueOf(xxx)
*/
@Test
public void test1(){
String str1 = "2696126203";
int num = Integer.parseInt(str1);
// 下面这两种方法都可以将基本数据类型转换成 String 型,转换后都保存在堆里。
String str2 = String.valueOf(num);
String str3 = num + "";
System.out.println(str1 == str3); // false
}
1.4.2 String 与字符数组之间的转换
/*
String 与 char[]之间的转换,String 的底层就是 char[]。
String --> char[]: 调用String的toCharArray()
char[] --> String: 调用String的构造器
*/
@Test
public void test2(){
String str1 = "hellosharm"; //题目: a21cb3
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h','e','l','l','o'};
String str2 = new String(arr);
System.out.println(str2);
}
1.4.3 String 与字节数组之间的转换
/*
String 与 byte[]之间的转换
编码:String --> byte[]:调用String的getBytes()
解码:byte[] --> String:调用String的构造器
编码:字符串 -->字节 (看得懂 --->看不懂的二进制数据)
解码:编码的逆过程,字节 --> 字符串 (看不懂的二进制数据 ---> 看得懂)
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。
*/
@Test
public void test() throws UnsupportedEncodingException {
String str1 = "abc123中国";
byte[] bytes = str1.getBytes();//使用默认的字符集,进行编码。
System.out.println(Arrays.toString(bytes));
byte[] gbks = str1.getBytes("gbk");//使用gbk字符集进行编码。
System.out.println(Arrays.toString(gbks));
System.out.println("******************");
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);//没有出现乱码。原因:编码集和解码集一致!
}
1.5 String 的常用方法
参考 JDK,阅读 JDK 是一种能力。
2 StringBuffer 和 StringBuilder
类 | 异同点 |
---|---|
String | 不可变的字符序列;效率最低;底层使用char[]存储 |
StringBuffer | 可变的字符序列;线程安全的,效率低;底层使用char[]存储 |
StringBuilder | 可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用char[]存储 |
StringBuffer 和 StringBuilder 包括 String 中的大部分方法,并且由于它们是可变的字符序列,所以对字符串内容进行增删时,不会产生新的对象。
两者均有增删改查方法,当使用 append 和 insert 时,如果原来 char 数组长度不够,可以自动扩容。扩容时,底层并不是真的用来存放数据的数组扩容了,而是用新的数组进行了替换。
一言以蔽之,在实际开发中,用的最多的是 StringBuffer 和 StringBuilder,然后通过项目是否需要多线程来决定选择两者中的哪一个。如果是多线程,则使用线程安全的 StringBuffer,反之,则使用 StringBuilder。