1. String内部声明
jdk8以前:
private final char value[];
- final:指明数组一旦初始化,其地址就不能改变
jdk9以后:
private final byte[] value;
,为节省内存空间而优化
- final:指明数组一旦初始化,其地址就不能改变
- 将char换成byte是为了节省空间,因为国外代码都是拉丁系列(英文)的文字,1byte保存一个character。
- 但是使用的是utf-16(中文)的话,使用2byte保存1character。
2. String的不可变性
不管什么操作改变的永远是引用地址,不是字符串!
“=”声明的字符串保存在字符串常量池中,new String()创建的字符串保存在堆中
"="声明的字符串变量,底层怎么操作的?
保存在字符串常量池中共。
重新赋值时:
- 不是覆盖原来的值,而是在常量池中给新字符开辟一个新的空间保存,并将这个空间地址指向这个引用。原来的字符常量依然存在,便于复用。
- 当别的引用被赋予这个原来的值时,将这个值的地址指向他即可,不会重新开辟空间。
字符串拼接时:
- 不是对原来字符串进行修改,而是
return new String(...)
创建了一个新的String对象。 - 不是保存在常量池中,因为是对象所以保存在堆中。
- 为了方便理解,这里对字符串保存方式做了简化,具体的下后边的 “通过new创建对象时:”
字符replace()替换操作:
- 不是对原来字符串进行修改,而是
return new String(...)
创建了一个新的String对象。 - 不是保存在常量池中,因为是对象所以保存在堆中。
-
- 为了方便理解,这里对字符串保存方式做了简化,具体的下后边的 “通过new创建对象时:”
new String()的方式创建的字符串,底层怎么操作?
保存在堆空间中,因为是对象。
通过new创建对象时:
虽然我们只new了一个对象,但底层是创建了两个对象。
- 一个对象是我们new的对象在堆中,用于保存字符串地址
- 另一个对象是字符串常量池的byte[]/char[]对象,用于保存字符串地址
- 字符串被单独放到了一块空间,并没有保存到某一个对象中,方便复用。
字符串还是保存在字符串常量池的,两个对象都通过value属性指向了字符串常量的位置。
注意: 内存中两个对象保存的相同字符串的地址是相同的,但是两个对象对应的引用地址是不行同的。
3. 字符串+拼接操作
由图可见,有变量参与的和字符串常量的拼接,底层的操作是不同的(比较值不一样)。
- 情况1:常量 + 常量:结果仍在常量池中(注:可能使字面量常量,也可能是final修饰的常量)
- 情况2:常量 + 变量 或 变量 + 变量:都会new一个新的字符串,保存在堆空间,返回堆空间地址
- 情况3:调用字符串的intern():返回的是字符串常量池中字面量的地址。
字面量字符串
对于字面量字符串,Java编译后会自动将其拼接。对于有变量参与的则不同。
所以对于字面量声明的字符串,不会创建对象,比较的是值,所以为true。
有变量参与的
有变量参与的,底层会通过new重新创建对象,所以引用保存了不同的内存地址。
所以 “==”比较的是内存地址,输出false。
底层通过StringBuilder.append()方法向StringBuilder添加元素,最后通过StringBuilder.toString()方法将StringBuilder转换成String(new String())。
intern()方法返回字符串常量池的地址
因为返回 的是地址,所以“==”结果为true
4. 字符串常量池
注意:字符串是引用数据类型,引用保存的是内存地址
- 我们声明的字符串常量都保存在字符串常量池中,
- 声明相同的字符串不会重复创建,而是给予相同的指针,
- jdk7之前常量池在方法区中,jdk7之后将其放到了堆空间
- 因为堆空间被GC垃圾回收比较频繁,便于及时清理不使用的字符串常量,节省内存空间。
5. String常用API
1. 构造器
- public String() :初始化新创建的 String对象,以使其表示空字符序列。
- String(String original): 初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。
- public String(char[] value) :通过当前参数中的字符数组来构造新的String。
- public String(char[] value,int offset, int count) :通过字符数组的一部分来构造新的String。
- public String(byte[] bytes) :通过使用平台的默认字符集解码当前参数中的字节数组来构造新的String。
- public String(byte[] bytes,String charsetName) :通过使用指定的字符集解码当前参数中的字节数组来构造新的String。
示例:
//字面量定义方式:字符串常量对象
String str = "hello";
//构造器定义方式:无参构造
String str1 = new String();
//构造器定义方式:创建"hello"字符串常量的副本
String str2 = new String("hello");
//构造器定义方式:通过字符数组构造
char chars[] = {'a', 'b', 'c','d','e'};
String str3 = new String(chars);
String str4 = new String(chars,0,3);
//构造器定义方式:通过字节数组构造
byte bytes[] = {97, 98, 99 };
String str5 = new String(bytes);
String str6 = new String(bytes,"GBK");
2. String与其他结构间的转换
字符串 --> 基本数据类型、包装类:
- Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
- 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。
基本数据类型、包装类 --> 字符串:
- 调用String类的public String valueOf(int n)可将int型转换为字符串
- 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类型到字符串的转换。
字符数组 --> 字符串:
- String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。
字符串 --> 字符数组:
- public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。
- public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。
字符串 --> 字节数组:(编码)
- public byte[] getBytes() :使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
- public byte[] getBytes(String charsetName) :使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
字节数组 --> 字符串:(解码)
- String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
- String(byte[],int offset,int length) :用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。
- String(byte[], String charsetName ) 或 new String(byte[], int, int,String charsetName ):解码,按照指定的编码方式进行解码。
3. 常用方法
常用方法
(1)boolean isEmpty():字符串是否为空
(2)int length():返回字符串的长度
(3)String concat(xx):拼接
(4)boolean equals(Object obj):比较字符串是否相等,区分大小写
(5)boolean equalsIgnoreCase(Object obj):比较字符串是否相等,不区分大小写
(6)int compareTo(String other):比较字符串大小,区分大小写,按照Unicode编码值比较大小
(7)int compareToIgnoreCase(String other):比较字符串大小,不区分大小写
(8)String toLowerCase():将字符串中大写字母转为小写
(9)String toUpperCase():将字符串中小写字母转为大写
(10)String trim():去掉字符串前后空白符
(11)public String intern():结果在常量池中共享
4. 查找
(13)boolean contains(xx):是否包含xx
(13)int indexOf(xx):从前往后找当前字符串中xx,即如果有返回第一次出现的下标,要是没有返回-1
(14)int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
(15)int lastIndexOf(xx):从后往前找当前字符串中xx,即如果有返回最后一次出现的下标,要是没有返回-1
(16)int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
5. 查找
(17)boolean contains(xx):是否包含xx
(18)int indexOf(xx):从前往后找当前字符串中xx,即如果有返回第一次出现的下标,要是没有返回-1
(19)int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
(20)int lastIndexOf(xx):从后往前找当前字符串中xx,即如果有返回最后一次出现的下标,要是没有返回-1
(21)int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。
6. 和字符/字符数组相关
(22)char charAt(index):返回[index]位置的字符
(23)char[] toCharArray(): 将此字符串转换为一个新的字符数组返回
(24)static String valueOf(char[] data) :返回指定数组中表示该字符序列的 String
(25)static String valueOf(char[] data, int offset, int count) : 返回指定数组中表示该字符序列的 String
(26)static String copyValueOf(char[] data): 返回指定数组中表示该字符序列的 String
(27)static String copyValueOf(char[] data, int offset, int count):返回指定数组中表示该字符序列的 String
7. 开头与结尾
(28)boolean startsWith(xx):测试此字符串是否以指定的前缀开始
(29)boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
(30)boolean endsWith(xx):测试此字符串是否以指定的后缀结束
8. 替换
(31)String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 不支持正则。
(32)String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
(33)String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
(34)String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。