String类
String类是Java中重要的类。
常用方法
字符串的构造
字符串有三种常用的构造方式:
Java的官方文档:https://docs.oracle.com/javase/8/docs/api/index.html
现在我们看一下,String是如何存储字符串的:
通过打断点,我们可以看到,每一个String类都有两个成员:一个是value,另一个是hash。(注意,可以看到字符串在存储的时候结尾是没有/0)
观察String类的源码,我们可以看到:String类是一个不可被继承的类,value是一个数组类型。
也就是说,String类中并没有存储字符串本身。
String类的存储方式
现在我定义了三个String类:str1, str2, str3. 让我们看一下在虚拟机内的存储情况。
可以看到,String类就是和类的存储情况一样。实际上,String类就是一种类,当然就和类的存储方式一样。但是此时涉及“常量池”的问题,后面再说。
对象名.length() 可以获取字符串的长度
也可以直接 字符串.length() 获取字符串长度
对象名.isEmpty() 可以查询字符串是否为空
字符串的比较
比较字符串是否相等
equals()
在字符串比较时,不能直接使用 == 判别。因为此时编译器比较的是两个字符串的引用数值,并不是真正比较字符串的内容。使用equals()来比较字符串里的内容。
equalsIgnoreCase()
忽略字符串大小写进行判等
String类里重写了equals方法
比较字符串的大小
compareTo()
按照每一个字符判断字符串的大小,如果都一样则判断字符串的长度,返回一个int整数。
compareToIgnoreCase()
忽略字符大小写比较
字符串查找相关方法
charAt() : 查找指定下标的元素
indexOf(int ch): 找到指定元素第一次出现的位置,没有返回-1
indexOf((int ch, int fromIndex): 从指定位置找元素第一次出现的位置,没有返回-1
int indexOf(String str):找到指定字符串出现的位置,返回字符串中第一个字符的下标
int indexOf(String str, int fromIndex): 从指定位置开始找指定字符串出现的位置,返回字符串中第一个字符的下标
int lastIndexOf(int ch):从后往前找指定字符出现的位置,没有返回-1
int lastIndexOf(int ch, int fromIndex):从指定位置从后往前找指定字符,没有返回-1
int lastIndexOf(String str): 从后往前找指定字符串的位置
int lastIndexOf(String str, int fromIndex): 从指定位置从后往前找指定字符串的位置
字符串转换
数值和字符串转换
valueOf():String有多种valueOf()方法使用
parse_xx():不同的包装类就有不同的parse*()方法来将字符串转成对应类型的数字。例如Integer里就有parseInt()方法将字符串转换成整型数字。
字符大小写转换
toUpperCase()、toLowerCase():将所给的字符串的字符转换成大/小写。假如该字符已经是大/小写,或者该字符没有大小写的说法,则不变。
字符串中的"///223232"什么都没有变
字符串转数组
toCharArray():将字符串转为数组
字符串格式化
format():将字符串格式化
字符串替换
replace():将字符串中的元素替换成新的元素
由上图可以看到,replace有两个方法,一个要传入的数据类型是"char",另一个是"CharSequence"。在下面的图中展示列String类继承的实现的接口,其中有CharSequence。
replace()将新元素替换了老元素。
注意:replace在替换的时候,并不是直接在原字符串里修改,而是生成一个新的字符串。
replaceAll()、replaceFirst():将所有(第一个)的字符串替换。
字符串拆分
split(String regex)、split(String regex, int limit):将字符串按照指定的元素进行切分。
注意:split()方法返回的对象类型为String[]
split()方法后面还可以填入想要拆分的个数。编译器会从所指定的元素开始拆分,直到数量满足要求就停下。
- 有些特殊字符在做拆分符号的时候需要加上转义字符 \\,例如 . | + *
2.当 \ 是分割符号时,需要写成 \\\\(字符串里也不允许只有一个\出现) (真难写,以后估计也不用)
3.如果有多个分割字符,用|进行表示
字符串截取
substring():指定截取范围。可以只指定起始位置,也可以起始位置和结束位置都指定(左左闭右开区间)。
其他操作
trim():去除掉字符串左右两边的空格,字符串中间的空格不管。
字符串常量池
在Java程序中,为了使得代码的运行效率更快,更节省内存,给8中基本类型和String类设置了常量池。
为了节省存储空间以及程序的运行效率,Java中引入了(简单了解即可):
- Class文件常量池:每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息
- 运行时常量池:在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
- 字符串常量池
字符串常量池底层是一个StringTable类实现的一个固定大小的HashTable。我们现在看如下代码:
出现这个结果的原因,是str1和str2均存在了字符串常量池中,而str3和str4则是新创建了对象。在JVM中的存储情况,如图所示:
首先,堆里创建了一个char[]类型的字符组“hello”,然后创建了一个String类,将字符组的地址0x10赋值给String类的value。然后,该String类的地址0x99分别赋值到str1和自负床常量池中(这里的操作并不是简单的地址赋值,而是放到了哈希表中,再将哈希值放到常量池中,但是这里不细说)。接下来,str2在创建的时候,在常量池中寻找有没有String类的value值是“hello”,发现存在,则直接将该String类的地址拿过来,因此,str1和str2里存的地址相同。
接下来,str3和str4的创建语句因为都有“new String”,因此它们俩都先在堆上创建了一个新的String类对象,然后将地址传到栈上。接下来,因为堆上已经有需要的字符组,因此,直接把该字符组的地址引用过来。因此,str3和str4里的值不相同,也不和str1、str2相同。
intern方法
该方法是将手动创建的String对象添加到常量池当中。
从上图中可以看到,使用intern方法之后,代码结果从false变成了true。此时,在第一个代码中,编译器的内部存储是这样的:
首先,在堆上创建了char[]类型的字符组"a b c",将地址传给栈上的ch。然后,创建String类的s1对象后,将堆上的“a b c”复制一份新的出来,并将新的字符组的地址赋给s1里的value,再将String
对象地址赋给栈上的s1。现在,"a b c"只是放在了对上,常量池里并没有。因此,在代码 String s2 = “abc”; 执行后,程序发现常量池里并没有“abc”,因此又在堆上生成了新的“abc”。所以,第一个图里的代码结果为false。
第二段代码是这样的,前两行代码不变,第三行代码 s1.intern(); 将0x20的字符组放到了常量池中。因此,在后面s2在生成String时,在常量池中发现了相同的内容,因此直接将地址0x99直接拿过来引用。因此,第二段代码的结果为true。
注意:当常量池中已经存在要放入的字符串时,字符串便不会再放入。如下图所示:
此时“abc”已经存在,intern操作便不再执行。
字符串的不可变性
String类型是不可变的,字符串中的内容是不可修改的。原因是String类里的value方法是被private和final修饰,而类里又没有提供get或者set方法。前面提到的对字符串的操作均是生成了一个新的字符串。
为什么 String 要设计成不可变的?(不可变对象的好处是什么?) (先简单看看)
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
StringBuilder和StringBuffer
由于String的不可修改性,Java提供了StringBuilder和StringBuffer这两个类来对字符串进行修改,也就是说,使用这两个类里的方法对字符串进行修改的时候,不会创建新的对象。
StringBuilder和StringBuffer这两个类里的方法大同小异,唯一的区别在于StringBuffer里的方法都被synchronized修饰。synchronized意为同步的,是多线程里“锁”的概念。也就是说,StringBuffer是线程安全的,而StringBuilder是线程不安全的。因次,StringBuffer多用于多线程,而StringBuilder多用于单线程。
并不是说StringBuffer是线程安全的,就比StringBuilder更好。频繁的开锁和上锁也要消耗大量的系统资源,因此,二者都有不同的业务场景。
- String、StringBuffer、StringBuilder的区别
String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.
StringBuffer与StringBuilder大部分功能是相似的
StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作