文章目录
String
1. 定义:不可变字符序列
- String类被
final
关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法; - 字符串一旦创建就不能再修改
- JDK 8 ,String实例的值是通过字符数组
char[] value
实现字符串存储的。 - JDK 9 ,String 内部改用 byte[] value 来存储数据
- 当给字符串重新赋值、连接concat、替换replace时,都需要重新制定内存区域,不能使用原有的value空间赋值。
- 当使用字面量给一个字符串赋值(区别于new),此时的字符串值声明在
字符串常量池中
。
2. 常用方法
判断方法
方法 | 说明 |
---|---|
boolean equals(Object obj) | 比较字符串内容是否相同,区分大小写 |
boolean equalsIgnoreCase(String str) | 比较字符串的内容是否相同,忽略大小写 |
boolean contains(String str) | 判断大字符串是否包含小字符串 |
boolean startsWith(String str) | 判断字符串是都以某个指定的字符串开头 |
boolean endsWith(String str) | 判断字符串是都以某个指定的字符串结尾 |
int compareTo(Object o) | 把这个字符串和另一个对象比较。 |
int compareTo(String anotherString) | 按字典顺序比较两个字符串。 |
int compareToIgnoreCase(String str) | 按字典顺序比较两个字符串,不考虑大小写。 |
boolean contentEquals(StringBuffer sb) | 当且仅当字符串与指定的StringBuffer有相同顺序的字符时候返回真。 |
boolean matches(String regex) | 告知此字符串是否匹配给定的正则表达式。 |
boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) | 测试两个字符串区域是否相等。 |
获取
public int indexOf(int ch) | 获取指定字符在此字符串中第一次出现处的索引。 | |
---|---|---|
public int indexOf(int ch, int fromIndex) | 获取指定字符在此字符串中第一次出现处的索引。 | |
public int indexOf(String str) | 返回指定字符串在此字符串中第一次出现处的索引 | |
public int indexOf(String str, int fromIndex) | 返回指定字符串在此字符串中从指定位置后第一次出现处的索引 | |
int lastIndexOf(int ch) | 返回指定字符在此字符串中最后一次出现处的索引。 | |
int lastIndexOf(int ch, int fromIndex) | 返回指定字符在此字符串中最后一次出现处的索引,从指定的索引处开始进行反向搜索。 | |
int lastIndexOf(String str) | 返回指定子字符串在此字符串中最右边出现处的索引。 | |
int lastIndexOf(String str, int fromIndex) | 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。 | |
char charAt(int index) | 返回指定索引处的 char 值。 | |
public String substring(int start) | 返回一个新的字符串,获取子串 | |
public String substring(int start, int end) | 返回一个新的字符串,获取子串 | |
CharSequence subSequence(int beginIndex, int endIndex) | 返回一个新的字符序列,它是此序列的一个子序列。 | |
String concat(String str) | 将指定字符串连接到此字符串的结尾。 |
byte[] getBytes() | 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 | |
---|---|---|
byte[] getBytes(String charsetName) | 使用指定的字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。 | |
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) | 将字符从此字符串复制到目标字符数组。 | |
int hashCode() | 返回此字符串的哈希码。 | |
String intern() | 返回字符串对象的规范化表示形式。 |
int length() | 返回此字符串的长度。 | |
---|---|---|
String replace(char oldChar, char newChar) | 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 | |
String replaceAll(String regex, String replacement ) | 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 |
String replaceFirst(String regex, String replacement) | 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 | |
---|---|---|
String[] split(String regex) | 根据给定正则表达式的匹配拆分此字符串。 | |
String[] split(String regex, int limit) | 根据匹配给定的正则表达式来拆分此字符串。 | |
static String valueOf(primitive data type x) | 返回给定data type类型x参数的字符串表示形式。 | |
char[] toCharArray() | 将此字符串转换为一个新的字符数组。 | |
static String copyValueOf(char[] data) | 返回指定数组中表示该字符序列的 String。 | |
static String copyValueOf(char[] data, int offset, int count) | 返回指定数组中表示该字符序列的 String。 | |
String toLowerCase() | 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。 | |
String toLowerCase(Locale locale) | 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写。 | |
String toString() | 返回此对象本身(它已经是一个字符串!)。 | |
String toUpperCase() | 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。 | |
String toUpperCase(Locale locale) | 使用给定 Locale 的规则将此 String 中的所有字符都转换为大写。 | |
String trim() | 返回字符串的副本,忽略前导空白和尾部空白。 |
String类型转换
- String和int的相互转换
//String 转 int
int i = new Integer(s).intValue()
int i = Integer.parseInt(String s);
//int 转 String
String s = i + "";
String s = new Integer(i).toString;
String s = String.valueOf(i);
//以下两者是等价的
s = i + ""
s = String.valueOf(i);
//以下两者也是等价的
s = "abc" + i;
s = new StringBuilder("abc").append(i).toString();
- array数组和string转换
//字符串转数组
char[] array = str.toCharArray();
//打印数组(数组转字符串)
String s = Arrays.toString(array);
- string转date
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//字符串转日期
Date date = format.parse("2020-12-10 12:00:00");
//日期转字符串
String str_date = format.format(date);
- 可以通过构造方法将其他基础类型转成字符串类型
String内存模型
1. String s = “abc” 和new String()的区别
String s1 = new String(“hello”) ,实际会创建两个对象。首先,在堆空间创建一个空间存储字符串,其次,会在常量池创建一个空间存储字符串常量值。最后 s1 引用堆空间的地址。
String s2 = “abc”,查找常量池中是否已存在该常量值,若不存在,直接在常量池开辟空间,s2 引用常量池中的地址。
2. intern() 方法
理解 intern 方法的作用
:如果一个字符串调用了 intern方法,那么JVM 就会去字符串常量池中寻找该字符串,若存在则直接返回常量池中相应Strnig的引用;
若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。因此,只要是等值的String对象,使用intern()方法返回的都是常量池中同一个String引用,
JDK 7 将常量池从永久代区移到了堆区,执行intern操作时,如果常量池已经存在该字符串,则直接返回字符串引用,否则复制该字符串对象的堆空间引用到常量池中并返回。由此堆中、常量池中使用的是同一份引用。
看下intern的使用
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2);//JDK 8 :false
String s1 = "hello";
String s2 = new String("hello").intern();
System.out.println(s1 == s2);//JDK 8 :true
3. new字符串过程会创建几个对象
* 思考:
* new String("ab")会创建几个对象? 答:看字节码,就知道是2个。
* 一个对象是:new关键字在堆空间创建的
* 另一个对象是:字符串常量池中的对象"ab"。 字节码指令:ldc
* 思考:
* new String("a") + new String("b")过程会创建几个对象呢? 答:6个
* 对象1:new StringBuilder()
* 对象2: new String("a")
* 对象3: 常量池中的"a"
* 对象4: new String("b")
* 对象5: 常量池中的"b"
*
* 深入剖析: StringBuilder的toString(), 并没有创建一个String对象
* 对象6 :new String("ab")
* 强调一下,通过字节码观察,StringBuilder的toString()的调用,在字符串常量池中,没有
生成"ab"
* 如何保证变量s指向的是字符串常量池中的数据呢?
* 有两种方式:
* 方式一: String s = "shkstart";//字面量定义的方式
* 方式二: 调用intern()
* String s = new String("shkstart").intern();
* String s = new StringBuilder("shkstart").toString().intern();
常见的一个判断误区
String s3 = new String("1") + new String("1");//指向堆空间
String s4 = "11";//指向常量池
String s5 = "11";//指向常量池
System.out.println(s3 == s4);//false
System.out.println(s3.equals(s4));//true,已重写
System.out.println(s5 == s4);//true
4. String之间的加法规则
变量相加先开空间再拼接,常量先拼接再找,没有就创建
- 常量与常量的拼接结果在常量池,原理是编译器优化
- 常量池不会存在具有相同内容的常量(原理是HashTable)
- 如果拼接时有一个变量,编译器就无法优化,运行时才能知道该变量的值。所以拼接结果在堆内存,原理是使用StringBuilder
- 如果拼接结果最后调用intern(),则主动检索常量池,只有常量池中没有该结果时,才将该结果优化到常量池。并且优化后常量池中的引用就等于堆内存的引用。
调用形式的图解:
注:由此可见,+ 号连接符在大量使用时由于创建多个StringBuilder实例会降低效率。
public class StringTest5 {
@Test
public void test1(){
String s1 = "a" + "b" + "c";//编译期优化:等同于"abc"
String s2 = "abc"; //"abc"一定是放在字符串常量池中,将此地址赋给s2
/*
* 最终.java编译成.class,再执行.class
* String s1 = "abc";
* String s2 = "abc"
*/
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true
}
@Test
public void test2(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";//编译期优化
//如果拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体的内容为拼接的结
果:javaEEhadoop
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
//intern():判断字符串常量池中是否存在javaEEhadoop值,如果存在,则返回常量池中
javaEEhadoop的地址;
//如果字符串常量池中不存在javaEEhadoop,则在常量池中加载一份javaEEhadoop,并返回
次对象的地址。
String s8 = s6.intern();
System.out.println(s3 == s8);//true
}
/*
如下的 s4 = s1 + s2; 的执行细节:(变量s是我临时定义的)
① StringBuilder s = new StringBuilder();
② s.append("a")
③ s.append("b")
④ s.toString() --> 约等于 new String("ab")
补充:在jdk5.0之后使用的是StringBuilder,在jdk5.0之前使用的是StringBuffer
*/
@Test
public void test3(){
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;//左右两边是变量
System.out.println(s3 == s4);//false
}
还记得final、static也会被编译器优化吗?
/*
1. 字符串拼接操作不一定使用的是StringBuilder!
如果拼接符号左右两边都是字符串常量或常量引用,则仍然使用编译期优化,即非StringBuilder的方式。
2. final、static都会被编译器优化,针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final的时候建议使用上。
编译期优化的意思是,在链接的prepare阶段变量就已经完成了默认初始化赋值操作,已经有等号右边的值了
*/
@Test
public void test4(){
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;//S1、S2 是使用final修饰的非变量
System.out.println(s3 == s4);//true
}
StringBuffer
String类是字符串常量
,是不可更改的常量。而StringBuffer是字符串变量
,它的对象是可以扩充和修改的。
StringBuffer是使用缓冲区的,本身也是操作字符串的,但与String类不同,String类中的内容一旦声明之后不可改变,改变的只是其内存地址的指向,而StringBuffer中的内容是可以改变的 。
获取长度
- 获取理想容量:capacity():默认初始容量是16
- 获取实际长度:length()
反转
注意:String类是没有这个功能的
- public StringBuffer reverse()
StringBuilder
StringBuilder是一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。
面试题
StringBuffer和数组的区别
- StringBuffer 的数据最终是一个字符串数据
- 数组可以存放多种数据类型,但必须是同一种数据类型的。
- StringBuffer是针对字符类型的
String、StringBuffer作为参数传递
抓住拼接的本质即可,String不可变,会另外开辟空间,StringBuffer操作本身!
/**
* 常见对象 String、StringBuffer分别作为参数传递
*/
public class Test01 {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "world";
change(s1,s2);
System.out.println(s1 + "-----" + s2);//hello-----world
StringBuffer sb1 = new StringBuffer("hello");
StringBuffer sb2 = new StringBuffer("world");
change(sb1,sb2);
System.out.println(sb1 + "-----" + sb2);//hello-----worldworld
}
public static void change(String s1,String s2){
//形参s1、s2由值传递,由于String的不可变性,s1 s2 会在当前方法中开辟新的拷贝
s1 = s2;//局部变量的引用改变,不影响原来的实参
s2 = s1 + s2;
}
public static void change(StringBuffer sb1,StringBuffer sb2){
sb1 = sb2;//局部变量sb1指向了另一个变量引用,不会影响实参
sb2.append(sb1);//sb2改变自己的变量空间的值,会影响实参
}
}