@:我所理解的String
java的string
java源码中的String
部分源码及注释
Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared
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
源码及注释解释
- 关于注释中的constant和immutable,指明String是不可变字符序列.对于源码实现,使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容。
- 关于shared,指明String对象是可共享的即线程安全的.
JVM内存管理简述
概述
Java虚拟机将其管辖的内存大致分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。方法区是静态分配(static allocation)的,编译器将变量在绑定在某个存储位置上,而且这些绑定不会在运行时改变。Java方法区的一个 重要部分,也是静态分配最典型的例子,是常数池,源代码中的命名常量、String常量和static 变量保存在其中。
栈(Stack) :存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)。
堆(heap):存放所有new出来的对象。
常量池(constant pool):在堆中分配出来的一块存储区域,存放储显式的String常量和基本类型常量(float、int等)。另外,可以存储不经常改变的东西(public static final)。常量池中的数据可以共享。
静态存储:存放静态成员(static定义的)
Java Stack
Java Stack是一个逻辑概念,特点是后进先出,此外没有特别的要求。Java Stack并不代表任何特定的内存区间,也不限制它的实现方式。一 个栈的空间可能是连续的,也可能是不连续的。最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就创建一个方法帧(frame),退出该 方法则对应的方法帧被弹出(pop)。栈分配存在一些局限:Java栈所处理的方法帧和局部变量,都将随着方法帧弹出而结束,显然局部变量无法从一个帧保 持到下一个帧,被调方法的帧不可能比调用方法的寿命更长。因此,从数据保存来看,栈分配适用于由作用域决定生命周期的局部变量。
Java Heap
Java堆(Heap)堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量和生命期在编译时无法确定的。Java对象的内存总是在heap中分配。
理解String
String的创建–new()与”“的区分
String类有一个特殊的创建方法,就是使用”“双引号来创建。例如new String(“123”)实际创建了2个String对象,一个是”123”通过”“双引号创建的,另一个是通过new创建的.只不过他们创建的时期不同,一个是编译期,一个是运行期。
String str1 = new String(“123”); ps:一般情况下,不要这样写..
String str2 = “123”;
第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。(实际是两个正如上文所说,但是在常量池中存在“123”后就不会再在常量池中创建新的“123”)
第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池里找有没有”abc”,如果没有,则将”abc”存放进字符串常量池,并令str指向”abc”,如果已经有”abc”则直接令str指向“abc”。这时我们应该注意
一方面,第一种写法有利与节省内存空间.同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“123”);的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。另一方面,我们在使用诸如String str = “123”;的格式定义类时,总是想当然地认为,创建了String类的对象str。
对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。1.
String a = “abc”;①
String b = “abc”;②
分析:
①代码执行后在常量池(constant pool)中创建了一个值为abc的String对象,②执行时,因为常量池中存在”abc”所以就不再创建新的String对象了。
2.
String c = new String(“xyz”);①
String d = new String(“xyz”);②
分析:①Class被加载时,”xyz”被作为常量读入,在常量池(constant pool)里创建了一个共享的值为”xyz”的String对象;然后当调用到new String(“xyz”)的时候,会在堆(heap)里创建这个new String(“xyz”)对象;②由于常量池(constant pool)中存在”xyz”所以不再创建”xyz”,然后创建新的new String(“xyz”)。
3.
String s1 = new String(“xyz”); //创建二个对象(常量池和栈中),一个引用
String s2 = new String(“xyz”); //创建一个对象(栈中),并且以后每执行一次创建一个对象,一个引用
String s3 = “xyz”; //创建一个对象(常量池中),一个引用
String s4 = “xyz”; //不创建对象(共享上次常量池中的数据),只是创建一个新的引用
String的比较equals和==
==比较的是2个对象的地址,而equals比较的是2个对象的内容
String a = "123"; String b = "123"; System.out.println(a==b); System.out.println(a.equals(b)); System.out.println("------------------------------------------"); /** * true * true * 此处创建一个字符串"123"储存在常量池中 * 因为"123"储存在常量区,并且唯一,即两个String引用a和b所的地址相同所以a==b为true * 并且两个引用在所指对象在堆中的内容相同所以a.equals(b)为true */ String c = new String("1234"); String d = new String("1234"); System.out.println(c==d); System.out.println(c.equals(d)); System.out.println("------------------------------------------"); /* * false * true * 此处创建三个字符串“1234”,一个在常量池中,两个通过new储存在堆中 * 因为c和d此时指向的是堆中的两个String对象,所以地址不同 c==d为false * 但是c与d堆中内容相同所以c.equals(d)为true */ String e = "a1"; String f = "a"+1; System.out.println(e==f); System.out.println(e.equals(f)); System.out.println("------------------------------------------"); /** * true * true * 此处创建“a1”“a”2个字符串其中“a1”“a”他们两个均在常量池中,你可能会问+是个运算符重载么? * 是的,java自己有一定的运算符重载但是你没法使用定义自己的运算符重载,和c++不同,String f = "a"+1; * 这句会被编译器做成 String f=“a1”;这与我们讲到的第一种情况相同,不再赘述。 * 编译器之所以这么做是因为他在编译时就能够确定 */ String g = "gh"; String hh = "h"; String h = "g" + hh ; System.out.println(g==h); System.out.println(g.equals(h)); System.out.println("------------------------------------------"); /** * false * true * 与上面不同的是这里的h在编译时不能确定(编译器是这样认为的),所以h所指的对象在运行时确定储存在堆中, * 所以g==h为true而g.equals(h)为false */
JAVA String为什么设计为不可变
常量池需要
- 字符串常量池的需要
字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。
如下面的代码所示,将会在堆内存中只创建一个实际String对象.[java] view plain copy
String s1 = “abcd”;
String s2 = “abcd”;假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.
请思考: 假若代码如下所示,s1和s2还会指向同一个实际的String对象吗?
String s1= “ab” + “cd”;
String s2= “abc” + “d”;
也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件.
允许String对象缓存hashcode
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。
安全性
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。
线程安全且效率高
String是不可变类,String对象的状态是不变的,所以线程安全。所有不可变类都是线程安全的,但是线程安全的类并不一定是不可变类。比如StringBuffer,是可变类,但它的线程安全是靠锁来保证的。因为string是不可变的,所以绝对安全。
StringBuilder实际上自身维护一个char[]数组,append是没有synchronized。StringBuffer的append等很多操作都是带有synchronized的,所以同样线程安全。所以单线程字符串拼接一般采用StringBuilder,效率高。多线程环境则采用Stringbuffer,虽然安全,但是相对效率会低些。
String StringBuffer StringBuilder
1)对于直接相加字符串,效率很高,因为在编译器便确定了它的值,也就是说形如”I”+”love”+”java”; 的字符串相加,在编译期间便被优化成了”Ilovejava”。这个可以用javap -c命令反编译生成的class文件进行验证。
对于间接相加(即包含字符串引用),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。
2)String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String
当然这个是相对的,不一定在所有情况下都是这样。
比如String str = “hello”+ “world”的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。
因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:
当字符串相加操作或者改动较少的情况下,建议使用 String str=”hello”这种形式;
当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。