部分参考:知乎:如何理解 String 类型值的不可变?胖君
String
一.String基本
1.1 内部存储结构
拿jdk1.8来说,它的内部本质是:String 内部实际存储结构为 char 数组
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** char数组存储字符 */
private final char value[];
/** 存放字符串的hashcode */
private int hash; // Default to 0
String为什么是不可变的?
- String
自身final
修饰,不可继承 private final
修饰的 char 类型数组存储字符(但是只是引用不可变,但是里面存的值是可变的)- String的方法里
很小心的没有去动Array里的元素
,没有暴露内部成员字段 - 就是对于数据的封装是很完善的
String是什么不可变的?
- String的不可变指的是:无法在原内存地址上修改数据
- 但是对象的指向是可以变的
- 通过反射是可以修改所谓的“不可变”对象,但是一般不用
用反射可以访问私有成员, 然后反射出 String 对象中的 value 属性, 进而改变通过获得的 value 引用改变数组的结构。
意义,好处
- 不可变对象不能被写,所以线程安全。
作为HashMap、HashTable等hash型数据key的必要
- 我们知道直接赋值,会产生在常量池中,是可以被多个对象一起指向的,
这样在大量使用字符串的情况下,可以节省内存空间,提高效率 ,要是内存里字符串内容能改来改去,这么做就完全没有意义了。
11. final 因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;
如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
12. 安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,
可能会有一系列校验,如果是可变类的话,最后的结果可能会发生偏差,造成崩溃
1.2 重要的4个构造方法
- String为参数
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
- char型数组为参数
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
- StringBuffer 未参数
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
- StringBuilder为参数
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
二. String的equals 与 ==
== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。
equals 比较的是字符串 的 字符 是否完全 相同
2.1 String产生位置的常识:
jdk1.8,这里只是这样说明,具体看2.4
- 在
右边直接用字符串赋值
的产生在常量池
中(常量池中的字符串不会重复),(包括用纯字符串相加的(就是用双引号引+起来的
)) - 其他赋值情况均是在
new(存在堆空间中)
- 而在比较时直接使用字符串(s4==“ab”),带双引号的
"ab"是在堆空间的
;
得出结论:加号的
1. string对象名 + string对象名是在 new字符串;
2. string对象名 + string字符串是在 new字符串;
3. string字符串 + string字符串是在 常量池中(如果没有,在常量池中创建,如果有则不用创建);
例子及答案
String st1 = "a";
String st2 = "b";
String st0 = "ab";
String st3 = st1+st2;
String st4 = "a"+"b";
String st5 = st1+"b";
String st6 = "ab";
System.out.println(st0 == st3); // f
System.out.println(st0 == st4); // t
System.out.println(st0 == st5); // f
System.out.println(st3 == st4); // f
System.out.println(st0 == st6); // t
System.out.println(st6 == "ab"); // t
2.2 易错点
String a1 = "";
String a2 = new String();
关于这一步,看源码,发现创建出的String 本质是 " " 与 a1相同:
Public String(){
this.value="".value;
}
String a3 = null ;
两个字符串都为 null , 那么这两个字符串==
例子答案:
String a1 ="";
String a2 =new String();
String a3 =null;
String a4 =null;
System.out.println(a1 == a2); // f
System.out.println(a1 == a3); // f
System.out.println(a2 == a3); // f
System.out.println(a4 == a3); // t
System.out.println("-------");
System.out.println(a1.equals(a2)); // t
System.out.println(a1.equals(a3)); // f
System.out.println(a2.equals(a3)); // f
System.out.println(Objects.equals(a3,a4)); // t
用equals比较时,推荐使用Objects.equals,可以防止抛出NullPointException异常。
System.out.println(Objects.equals(a3,a4)); // t
final的易错点
String str0 = "ab";
final String str1 = "a";
final String str2 = "b";
String str3 = str1+str2;
System.out.println(str0 == str3);
根据上面的总结:这是在new 对象,答案应该是 false
但那是错误的,答案是true
因为final修饰的对象编译后会直接替换成对应的值,就是你使用的str1
,其实已经变为 "a"
String str3 = str1+str2;
// 等价为
String str3 = "a"+"b";
// 所以是在常量池中的"ab"
2.3 Object的equals
equals()比较的是对象的 内存地址是否相等
public boolean equals(Object obj) {
return (this == obj);
}
- Object 中的 equals() 方法其实就是
==
- 而 String 重写了 equals() 方法把它修改成
比较两个字符串的值是否相等
。
2.4 String两种创建方式的差别,内存分配(1.6,1.7及以后的区别)
理解java字符串内存分配及常用操作:作者:一刻动画
String 对象创建方式有哪几种?有什么区别?:本文出自水货程序员的腾讯云博客
String内存分配和intern方法:本文出自zhh_happig的简书博客
String s1 = new String("hello");
String sIntern = s1.intern(); // 入池操作
String s2 ="hello";
System.out.println(s1 == s2); // f
System.out.println(s2 == sIntern); // t
String s3 = new String("hello") + new String(" world");
String intern = s3.intern();
String s4 = "hello world";
System.out.println(s3 == s4); // t
System.out.println(s3 == intern); // t
String s5 = new String("ja") + new String("va");
String intern1 = s5.intern();
String s6 = "java";
System.out.println(s5 == s6); // f
System.out.println(s5 == intern1); // f
/**
* 常量池在初始化的时候会内置一些字符串常量进去,
* 在 rt.jar 里面已经用到了 "java" 这个字符串
*/
优化性能
把一个基本数据类型转为一般有三种方式,一个Integer型数据i,可以使用i.toString
、String.valueOf(i)
、i+””
三种方式,三种方式的效率如何;
i.toString
>String.valueOf(i)
>i+””
- String.valueOf方法底层
调用了Integer.toString
方法,会在调用前做空判断 - i + “”底层使用了
StringBuilder
实现,先用append方法
拼接,再用toString方法
获取字符串
三.常用方法的解释
四. 易错方法的解释总结
split
String s1 = "333111333111333";
String [] arr = s1.split("3");
System.out.println(Arrays.toString(arr));
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i].length());
}
[, , , 111, , , 111]
0
0
0
3
0
0
3
画图理解分割