String
String是不可变的,注意这里的不可变是修饰对象,String的值是可以变的
String s=”abcd”;//一个对象 String s=”abcdel;//另一个对象
String 是被声明为final类,不可被继承。 不提供setter方法, 所有的字段均为final、private。 构造函数也是private。(这里可以引出 构造函数可以有public、priavte 但是没有返回值,必须是void的)
存储
在 Java 8 中,String 内部使用 char 数组存储数据。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder 来标识使用了哪种编码。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
其value为fianl修饰,说明在初始化之后就不能再引用其他数组,同时String内部无修改value数组的方法,因此保证了String是不可变的。
不可变的好处
- 可以缓存hash值
因为 String 的 hash 值经常被使用。 例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
- String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool(位于堆中) 中取得引用。 只有 String 是不可变的,才可能使用 String Pool。
- 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
- 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String的equals()和hashCode()
这里我们特别注意字符串的equals()的调用,String::equals 是覆盖了Object的equals函数
只要字符串内容相同,字符串则equals()相等
这里我们特别注意字符串的hashcode的调用,String::hashCode 是覆盖了Object的hashCode函数
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
从代码可以看出,只要他们value一致,他们的hashCode()就是一致的。但是他们的hashCode()一致,不能说明他们value一致,比如字符串”Aa”和”BB”。(在理想情况下:为不同的对象产生不相等的hashCode,但是这种理想情形是非常困难的,因为hachCode的生成是非随机生成的,它有一定的规律,同时这个规律就是每个hash算法的优势所在。))
特点: 相同前缀的字符串所生成的hash值是相邻的
String StringBuffer String Builder
String
是不可变字符串,位于常量池中。
StringBuilder
与StringBuffer
是两个常用的操作字符串的类,他们是一种可变字符串(具有扩容机制,需要预设定大小,默认值为16),位于堆中。
StringBuilder
是线程不安全的,而StringBuffer
是线程安全的,这意味着StringBuffer中许多操作都用到了Synchronized
修饰。
字符串常量池
字符串常量池的诞生是为了提升效率和减少内存分配。可以说我们编程有百分之八十的时间在处理字符串,而处理的字符串中有很大概率会出现重复的情况。
字符串常量池是在堆
中。
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。
JVM为了减少字符串对象的重复创建,维护了一段特别的内存称为字符串常量池,每次通过字面量形式创建字符串时,会首先取字符串常量池中读取是否已存在,已存在则将引用返回。
我们要首先认识到String 是不可变的,是final修饰的。因此多个变量可以共享同一个对象,由此才有字符串常量池的说法。同时因为是final不可变类型,也出现了有效防止字符串被修改的情况
当然String对象的不可变设计,可以在Hash类型进行缓存hashCode时,执行效率提升。
final String h="sunlingzhi";
String s="sunlingzhi";//指向常量池
String y="sunlingzhi";
System.out.println(s==y);//true
String z=new String("sunlingzhi");//指向堆空间
System.out.println(s==z);//false s和z不是同一个对象
String v=s+y; //stringBuilder.append() 非final字段在运行期进行赋值处理 指向堆
String a=h+"sunlingzhi"//常量池 final字段编译期会进行常量替换 指向常量池
//额外注意hashCode和equals函数
//s、y、z 他们三者两两的以上函数 都为true
string的 intern() 方法
检测字符串常量池是否存在对应字符串,若存在,则返回池中的字符串,若不存在,该方法会添加原字符串在存储堆中的引用到字符串池,再返回该引用。
String a=new String("test");//指向堆 创建了两个对象,常量池中的”test”和堆中对象。
String s=a.intern();//指向常量池
System.out.println(s==a);//false
String str2 = new String("str")+new String("01");//str 01 都会进入常量池,字符串的+ 是通过StringBuilder append 后 再 ToString()的 ,str01只会在堆中
str2.intern();//此时常量池不存在str01 因此intern之后,添加原字符串在存储堆中的引用到字符串池,再返回该引用。
String str1 = "str01";//str01在常量池中的引用 是指向堆中的字符串“str01”
System.out.println(str2==str1);//true
new String(“abc”)
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。
- “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
- 而使用 new 的方式会在堆中创建一个字符串对象。
创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。
创建包括一个堆对象,包括常量池中的abc
。
public class NewStringTest {
public static void main(String[] args) {
String s = new String("abc");
}
}