1.其定义的字符串序列不可变。java
2.是一个final类,不可被继承,且其内部一些重要方法被定义为final类型,不可重写。安全
3.内部实现Serializable接口(支持字符串序列化)和Comparable接口(支持字符串比较大小)。性能优化
4.内部定义了final char [ ] value 用于存储字符串数据。jvm
1.字面量赋值的形式实例化: String str1 = "abc" 2.用 new + 构造器形式实例化: String str2 = new String("abc")
下面来分析一下两种不一样实例化方式的区别:性能
当咱们执行System.out.println(s1 == s2);
的时候,输出结果为`false
,优化
而执行System.out.println(s1.equals(s2));
的时候,输出结果为true
,线程
这和虚拟机的内存分配有关:
code
对于str1字面量赋值的形式来讲,字符串常量是存放在常量池中。而对于str2的构造器赋值形式,堆中的value存放的是new String("abc")
对象自己,而str2是栈中开辟的一个内存块,他里面存放了指向对象自己的引用地址。有一点须要知道,在常量池中存放的东西都是惟一的,不会出现两个相同的内容,这也是为了减小内存开销和提高jvm的性能优化,因此在使用str2 的时候,对象自己又会到常量池中找是否有abc
,若是没有则建立新的,若是有,则直接使用。对象
在以前的文章中也探究过==
和equals
的区别,当用==比较的时候,对于基本数据类型,比较的是内容,值是否相等。而对于刚刚的str1和str2,他们都是引用型数据类型,用==比较的时候,比较的是地址,很明显,str1的地址直接指向常量池中的abc,而str2 的地址是指向堆内存中的实例对象,因此==比较确定是false,而用equals比较的时候,结果为true,这是由于String类对object类的equals方法进行了重写,object类中的equals方法底层用的仍是==来判断地址值。blog
总结区别:
1.字面量赋值的形式实例化,字符常量内容存于常量池,变量存于栈中,直接指向常量池。
2.new + 构造器形式实例化,会先在堆中建立实例对象,引用对象存于栈中,而后再去常量 区寻找须要的字符常量,若是找到了,直接使用,没找到则开辟新的空间并存储内容。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
能够看出,String是一个final
类,也就意味着他不能够被继承,并且其内部成员变量是private
修饰,方法也是final
修饰,一样也就意味着他的成员变量不会直接暴露给用户,方法不能够被重写。这样实现了方法不可变(不能重写),变量不能变,好比private final char value[];
这里就是string字符串不能变的实现。
字符串不可变底层实现分析:
当运行以下代码的时候:
String s1 = "Java"; String s2 = "Hello"; String s5 = s1; s5 = "change"; String s3 = new String("Hello"); String s4 = new String("Java");
字符串常量在虚拟机内存空间的状况如图所示:
可见,对于String s1 = "Java"
这种字面量赋值的形式,会直接在常量池中开辟一个空间用于存储相应的字符串(前提是常量池中尚未该字符串),而String s3 = new String("Hello")
这样的,会先在堆中建立对象,而后再去常量池中找是否有须要的字符常量,若是有,则直接使用,若是没有,也一样须要开辟新的空间来存储。
重点看 :
String s1 = "Java"; String s5 = s1; s5 = "change";
当执行String s5 = s1
时,s5会直接去使用s1在常量池中的内容,然后面当执行s5 = "change"
的时候,也就是说须要对Java这个字符串进行修改,但是这个字符串除了s5本身使用外,s1也在使用,因此就不能直接修改他,而是要在空间中从新开辟一个空间,用于存储change
。这就是字符串不能够直接修改的底层实现!
字符串设置为不可变的缘由:
①出于安全考虑,程序在运行以前虚拟机会把字符常量,静态变量等预加载到常量池(方法区) 中存储起来,在程序运行的时候直接调用,可是常量池里面的信息不会有重复的,每个都是 惟一的(这样是为了减小内存的开销,提高性能),这些信息是线程共享的,同一个字符串可 能会被多个线程使用,若是字符串可变,当某个线程修对他作了修改,其余正在使用该字符串 的线程可能就会出现严重的错误,从而变得不安全。
②保证hash值不会常常变更,具备惟一性,使得相似HashMap的容器能实现key—value的功能
static String s1 = "Hello"; static String s2 = "Java"; static String s3 = "Hello"+"Java"; static String s4 = "HelloJava"; static String s5 = s1 + "Java"; static String s6 = "Hello" + s2; static String s7 = s1 + s2; static String s8 = (s1 + s2).intern();内存分配如图:
字符串拼接总结:
1.常量和常量的拼接,结果也在常量池中,且不存在两个相同的常量。
2.只要参与拼接的项里面有变量,结果就在堆中。
3.使用(String).inter()
方法处理拼接后,被处理的字符串会进入常量池中。
文章仅是笔者的我的理解,不免存在许多不完善和理解不恰当之处,欢迎批评指正。
码字不易,创做辛苦,欢迎转载分享,请注明出处。
交流欢迎Q我:321662487