第1节 常用类库——String
因为String
相对之前的类来说更加常用一些,所以对字符串类进行专门的整理。
1. 概述
String类表示字符串,Java中的所有字符串文字都实现为此类的实例。
字符串是不变的,它们的值在创建后无法更改 。因为它的创建是通过一个由private final
修饰的byte
数组value
来实现的,一旦赋值,不可更改。
如果想要使用可变字符串,后面的StringBuffer
和StringBuilder
会介绍。
因为String对象是不可变的,所以可以共享它们。 这句话怎么理解?需要结合下面的赋值方式来理解。
2. String的两种赋值方式:
- 一种为直接赋值,因为一个字符串可以表示一个
String
类的对象,
String text = "123";
- 第二种为通过关键字
new
调用String
的构造方法赋值:
String text3 = new String("123");
先来看一段代码,体会一下两者的区别:
package com.kaikeba.coreclasslibrary.string;
public class Demo {
public static void main(String[] args) {
String text = "123";
String text2 = "123";
String text3 = new String("123");
System.out.println(text == text2);
System.out.println(text == text3);
}
}
结果为:
true
false
这里就用到了1中说的因为String对象是不可变的,所以可以共享它们。
因为String在内存中存储在永久代中(后续会介绍),该内存不会去进行GC,所以没有必要给同样的字符串分出两块地址,使用第一种赋值方式,并不会开辟出两块地址,而是会将两个引用对象指向同一个地址 ,所以text和text2就是一样的值,这就是共享的意思。 但是第二种赋值方式,使用new关键字强制地开辟了一块内存,所以地址值不一样。
给一张图会更直观一些,str1就是上述代码的text3,str2和str3就是上述代码的text和text2。
3. 字符串常量池
3.1 方法区(Method Area)
方法区(Method Area),又称永久代(Permanent Generation),又称非堆区(Non-Heap space)。
方法区是被所有线程共享的:
- 所有字段和方法字节码,以及一些特殊方法和构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间 。
- 该区域存储的是:静态变量+常量+类信息(构造方法/接口定义)+运行时常量池。
但是,实例变量(对象)存在堆内存中,和方法区无关。
以上,只是逻辑上的定义,在HotSpot中,方法区仅仅只是逻辑上的独立,实际上还是包含在Java堆中,也就是说,方法区在物理上属于Java堆区中的一部分,而永久区(Permanent Generation)就是方法区的实现。
3.2 堆(heap)
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行。
堆 在逻辑上分为三部分(Perm):
- 新生代 (Young Generation,常称为YoungGen):刚创建的内存存储位置,频繁地进行GC,若一个对象连续经过15次GC还没有被回收,它就会被放到老年代。位于堆空间 。
- Eden区 :存储新创建的对象;
- Survior区 (幸存区):存储GC次数1~14次的对象。
-
老年代 (Old Generation。常称为OldGen、TenuringGen):GC的频次较低,位于堆空间 。
-
永久代 (Permanent Generation,常称为PermGen):是一个常驻内存区域,用于存放JDK自身所携带的Class、Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装在进此区域的数据是不会被垃圾回收器回收的,关闭JVM才会释放此区域所占用的内存。(不进行GC,类、方法、常量、静态修饰的内容都会放在永久代 )位于非堆空间。
4. 构造方法
最常用的当然还是直接给一个字符串而不是用构造器。
此外还需要注意三个构造方法:
1、构造一个新的String
并用指定的解码集解码,读入不同集的字符串会需要。
2、3:用一个StringBuffer
或StringBuilder
对象来构造String
,这个可以实现StringBuffer
或StringBuilder
到String
类的转变。
5. 各种方法
String
类的方法很多。。。
1、返回指定索引处的char
值,这还是很常用的,因为Java字符串不能像数组一样使用[索引]来访问,charAt
方法就是替代。IntStream
类先跳过,没学过。
2、返回Unicode
的方法,目前还没用过。
3、按字典顺序比较两个字符串,不用多解释。
4、拼接字符串,返回拼接后的结果。
5、功能很明确
6、字符串与字符序列或StringBuffer
比较是否相等。
7、判断字符串是否以指定的后缀结尾。
8、比较是否相等
9、指定的格式字符串和参数返回格式化字符串,没用过。
10、将字符串变为数组
11、返回字符串的哈希码
12、返回字符第一次出现的索引
13、啥是规范表示?
14、判断字符串是否为空
15、将字符序列按照指定的连接符拼接为字符串,这个很常用 。
16、返回字符最后一次出现的索引
17、返回字符串的长度,常用
18、现在不懂
19、判断字符串是否与指定的正则表达式匹配,没用过
20、不懂
21、判断两个字符串区域是否相等
22、返回该字符串重复了count次的字符串
23、字符串替换
24、字符串拆分,按照指定的拆分符
25、判断字符串是否以指定前缀开头
26、去除头尾的空格
27、取子字符串
28、将字符串转为字符数组
29、大小写
30、和strip类似,去除的不只空格
31、将数据类型转为字符串,注意是静态方法,直接用类名调用
6. 字符串拼接问题
观察如下代码:
package com.kaikeba.coreclasslibrary.string;
public class Demo {
public static void main(String[] args) {
String t1 = "锄禾日当午";
String t2 = "汗滴禾下土";
String t3 = "床前明月光";
t1 = t1+t2+t3;
System.out.println(t1);
}
}
它在内存中的细节如下图所示:
通过“+”运算符会产生很多中间字符串,而且没有引用,另外因为字符串是存在字符串常量池,也就是在永久代中的, 所以GC不会来回收它们,这样就导致了一个严重的问题,内存占用过大 。
7. StringBuffer、StringBuilder类
为了解决上述的问题,就需要用到另外两个类StringBuffer
和StringBuilder
。这两个都是长度、内容可变的字符序列类型,字符串缓冲区类似于String,但是可以进行修改。它们两个方法上上很相似,主要区别是:StringBuffer
是线程安全的,StringBuilder
是线程不安全的,单个线程情况下,StringBuilder
更快,线程的问题后续再介绍。
7.1 构造方法
两者是一样的,重点关注将String
类直接转为相应的类的构造方法。
7.2 方法
最常用的就是append和insert方法,插入新数据:
有加入就有删除:
还有设置:
注意获取长度是capacity:
还有一个需要特别记忆,翻转操作,String类没有:
还有一个需要特别注意,它们的toString
方法已经被重写,调用toString
直接将这两类转为String
返回:
其他的方法很多String
类的都有。
看一个简单例子:
package com.kaikeba.coreclasslibrary.string;
public class Demo2 {
public static void main(String[] args) {
//线程不安全的实现
StringBuilder sb = new StringBuilder();
//StringBuffer 线程安全的实现
sb.append("1");
sb.append("2");
sb.append("3");
String text = sb.toString();
System.out.println(text);
}
}
结果为:
123