String类的特点是什么?
概览
String 被声明为 final,因此它不可被继承。(Integer 等包装类也不能被继承)
在java8中,String内部以char[]数组的形式存储数据;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
String 常见的创建方式:
- String s1 = “java”; 从String pool寻找,返回句柄
- String s2 = new String(“java”)
第一种方式,从String pool寻找java,没有则在pool中创建,返回句柄;
第二种方式,会直接在堆(1.7后)创建String对象,只有调用了intern()方法,才会放入String pool;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SYCt7EBY-1593684383098)(/home/picture/1.png)]
JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。
除此之外编译器还会对String字符串做一些优化:
String s1 = "ja"+"va";
String s2 = "java";
System.out.pringln(s1 == s2); //结果为true
不可变
value数组被生命成了final,意味着value一旦初始化后便不可指向其它内存地址,并且String内部,没有改变value内容的任何方法。且String类为final类,避免了子类重写或修改String对象属性;
保护性拷贝:
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
这是以char数组为参数的一个构造方法,它并没有把这个数组引用直接赋值给实例对象的value成员变量,而是通过一个Arrays.copyOf的方式拷贝一个数组再给到对象的成员变量,防止外部引用执行同一个地址而修改该数组内容。
不可变的好处
1.Requirement of String Pool
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
以下代码将在堆中仅创建一个字符串对象。
字符串 string1 = “ abcd” ;
字符串 string2 = “ abcd” ;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X4Nu5xMj-1593684383101)(/home/picture/1.png)]
2.Caching Hashcode(缓存HashCode)
字符串的哈希码在Java中经常使用。例如,在HashMap或HashSet中。不可变保证哈希码始终是相同的,这意味着无需每次使用哈希码就进行计算,这样更有效。
3.安全
字符串被广泛用作许多java类的参数,例如网络连接,打开文件等。如果字符串不是不可变的,则连接或文件将被更改,这可能会导致严重的安全威胁,可变字符串也可能在反射中引起安全问题。
4.线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String pool
字符串常量池保存着所有字符串字面量,这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。
主要使用方式:
- 直接使用双引号声明出来的String对象会直接存储在常量池中。
- 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
String s1 = "aaa";
String s2 = "aaa";
System.out.println(s1 == s2); // true
如果采用"aaa"这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。
intern() 方法:当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
它的大体实现结构就是: JAVA 使用 jni 调用c++实现的StringTable的intern方法, StringTable的intern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。
要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。
在 jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:
-XX:StringTableSize=99991
new String(“abc”)
上述的语句中是创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。
String, StringBuffer and StringBuilder
1. 可变性
String 不可变
StringBuffer 和 StringBuilder 可变
2. 线程安全
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步
常用方法
1. equals()
用于比较两个字符串是否相等,比较时会先通过instanceof 判断是否为String类型,如果不是直接反悔false,否则循环比较每一个字符