本文原创作者:
作者博客地址:
1、字符串池
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM 为了提高性能和减少内存开销,在实例化字符串字面值的时候进行了一些优化。为了减少在 JVM 中创建的字符串的数量,字符串类维护了一个字符串常量池,每当以字面值形式创建一个字符串时,JVM 会首先检查字符串常量池:如果字符串已经存在池中,就返回池中的实例引用;如果字符串不在池中,就会实例化一个字符串并放到池中。Java 能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。 例如:
- public class Program
- {
- public static void main(String[] args)
- {
- String str1 = "Hello";
- String str2 = "Hello";
- System.out.print(str1 == str2); // true
- }
- }
一个初始为空的字符串池,它由类 String 私有地维护。当以字面值形式创建一个字符串时,总是先检查字符串池是否含存在该对象,若存在,则直接返回。此外,通过 new 操作符创建的字符串对象不指向字符串池中的任何对象。
2、 手动入池
一个初始为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
它遵循以下规则:
对于任意两个字符串 s 和 t ,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true 。
- public class TestString
- {
- public static void main(String args[]){
- String str1 = "abc";
- String str2 = new String("abc");
- String str3 = s2.intern();
- System.out.println( str1 == str2 ); //false
- System.out.println( str1 == str3 ); //true
- }
- }
所以,对于 String str1 = "abc",str1 引用的是常量池(方法区)的对象;而 String str2 = new String("abc"),str2 引用的是堆中的对象,所以内存地址不一样。但是由于内容一样,所以 str1 和 str3 指向同一对象。
3、小结
- public static void main(String[] args) {
- /**
- * 情景一:字符串池
- * JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象;
- * 并且可以被共享使用,因此它提高了效率。
- * 由于String类是final的,它的值一经创建就不可改变。
- * 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。
- */
- String s1 = "abc";
- //↑ 在字符串池创建了一个对象
- String s2 = "abc";
- //↑ 字符串pool已经存在对象"abc"(共享),所以创建0个对象,累计创建一个对象
- System.out.println("s1 == s2 : "+(s1==s2));
- //↑ true 指向同一个对象,
- System.out.println("s1.equals(s2) : " + (s1.equals(s2)));
- //↑ true 值相等
- //↑------------------------------------------------------over
- /**
- * 情景二:关于new String("")
- *
- */
- String s3 = new String("abc");
- //↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;
- //↑ 还有一个对象引用s3存放在栈中
- String s4 = new String("abc");
- //↑ 字符串池中已经存在"abc"对象,所以只在堆中创建了一个对象
- System.out.println("s3 == s4 : "+(s3==s4));
- //↑false s3和s4栈区的地址不同,指向堆区的不同地址;
- System.out.println("s3.equals(s4) : "+(s3.equals(s4)));
- //↑true s3和s4的值相同
- System.out.println("s1 == s3 : "+(s1==s3));
- //↑false 存放的地区都不同,一个方法区,一个堆区
- System.out.println("s1.equals(s3) : "+(s1.equals(s3)));
- //↑true 值相同
- //↑------------------------------------------------------over
- /**
- * 情景三:
- * 由于常量的值在编译的时候就被确定(优化)了。
- * 在这里,"ab"和"cd"都是常量,因此变量str3的值在编译时就可以确定。
- * 这行代码编译后的效果等同于: String str3 = "abcd";
- */
- String str1 = "ab" + "cd"; //1个对象
- String str11 = "abcd";
- System.out.println("str1 = str11 : "+ (str1 == str11));
- //↑------------------------------------------------------over
- /**
- * 情景四:
- * 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。
- *
- * 第三行代码原理(str2+str3):
- * 运行期JVM首先会在堆中创建一个StringBuilder类,
- * 同时用str2指向的拘留字符串对象完成初始化,
- * 然后调用append方法完成对str3所指向的拘留字符串的合并,
- * 接着调用StringBuilder的toString()方法在堆中创建一个String对象,
- * 最后将刚生成的String对象的堆地址存放在局部变量str3中。
- *
- * 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。
- * str4与str5地址当然不一样了。
- *
- * 内存中实际上有五个字符串对象:
- * 三个拘留字符串对象、一个String对象和一个StringBuilder对象。
- */
- String str2 = "ab"; //1个对象
- String str3 = "cd"; //1个对象
- String str4 = str2+str3;
- String str5 = "abcd";
- System.out.println("str4 = str5 : " + (str4==str5)); // false
- //↑------------------------------------------------------over
- /**
- * 情景五:
- * JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。
- * 运行期的两个string相加,会产生新的对象的,存储在堆(heap)中
- */
- String str6 = "b";
- String str7 = "a" + str6;
- String str67 = "ab";
- System.out.println("str7 = str67 : "+ (str7 == str67));
- //↑str6为变量,在运行期才会被解析。
- final String str8 = "b";
- String str9 = "a" + str8;
- String str89 = "ab";
- System.out.println("str9 = str89 : "+ (str9 == str89));
- //↑str8为常量变量,编译期会被优化
- //↑------------------------------------------------------over
- }
1.String 与 StringBuilder
简要的说, String 类型 和 StringBuilder 类型的主要性能区别在于 String 是不可变的对象。 因此,在每次对 String 类型进行改变时,其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。所以,经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。而如果是使用 StringBuilder 类则结果就不一样了,每次结果都会对 StringBuilder 对象本身进行操作,而不是生成新的对象并改变对象引用。所以,在一般情况下,推荐使用 StringBuilder ,特别是字符串对象经常改变的情况下。
而在某些特别情况下,String 对象的字符串拼接可以直接被 JVM 在编译器确定下来。所以,这时在速度上,StringBuilder 不占任何优势。
- String S1 = "This is only a" + " simple" + " test"; //编译期完成字符串常量的串联,相当于"This is only a simple test"
- StringBuffer Sb = new StringBuilder("This is only a").append("simple").append(" test");
对于
- String S1 = "This is only a" + " simple" + "test";
其实就是:
- String S1 = "This is only a simple test";
需要注意的是,如果是下面的情形,其内部实现是先 new 一个 StringBuilder,然后调用其 append 方法连接,效率会较低。
- String S2 = "This is only a";
- String S3 = " simple";
- String S4 = " test";
- String S1 = S2 +S3 + S4;
因此,在大部分情况下, 在效率方面:StringBuilder > String .
2.StringBuffer 与 StringBuilder
首先,JDK 的实现中 StringBuffer 与 StringBuilder 都继承自 AbstractStringBuilder。AbstractStringBuilder 的实现原理为:AbstractStringBuilder 中采用一个 char 数组 来保存需要 append 的字符串,char 数组有一个初始大小,当 append 的字符串长度超过当前 char 数组容量时,则对 char 数组进行动态扩展,即重新申请一段更大的内存空间,然后将当前 char 数组拷贝到新的位置,因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式,这里是 2 倍。
【
StringBuffer 始于 JDK 1.0
StringBuilder 始于 JDK 1.5
从 JDK 1.5 开始,对含有字符串变量 (非字符串字面值) 的连接操作(+),JVM 内部采用的是
StringBuilder 来实现的,而之前这个操作是采用 StringBuffer 实现的。
】
Java.lang.StringBuffer 是线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
java.lang.StringBuilder 也是一个可变的字符序列,是 JDK 5.0 新增的。此类提供一个与 StringBuffer 兼容的 API,即:StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
因此,在单线程下,优先使用 StringBuilder.
对于三者使用的总结:
- String s = "a" + "b" + "c";
- String s1 = "a";
- String s2 = "b";
- String s3 = "c";
- String s4 = s1 + s2 + s3;
分析:变量 s 的创建等价于 String s = "abc"; 由上面例子可知编译器进行了优化,这里只创建了一个对象。由上面的例子也可以知道; s4 不能在编译期进行优化,其对象创建相当于:
- StringBuffer temp = new StringBuffer();
- temp.append(s1).append(s2).append(s3);
- String s = temp.toString();
由上面的分析结果,可就不难推断出 String 采用连接运算符(+)效率低下原因分析,形如这样的代码:
- public class Test {
- public static void main(String args[]) {
- String s = null;
- for(int i = 0; i < 100; i++) {
- s += "a";
- }
- }
- }
每做一次 + 就产生一个 StringBuilder 对象,然后 append 后就扔掉。下次循环再到达时重新 new 一个 StringBuilder 对象,然后 append 字符串,如此循环直至结束。如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以,对于在循环中要进行字符串连接的应用,一般都是用 StringBulider 对象来进行 append 操作。
1、基础
- class Employee implements Cloneable
- {
- public Object clone() throws CloneNotSupportedException {
- Employee cloned = (Employee) super.clone();
- cloned.hireDay = (Date) hireDay.clone() ; //public class Date implements java.io.Serializable, Cloneable, Comparable<Date>
- return cloned;
- }
- }
因此,Object 在对某个对象实施 Clone 时,对其是一无所知的,它仅仅是简单执行域对域的 Copy. 其中,对八种基本类型的克隆是没有问题的,但当对一个对象进行克隆时,只是克隆了它的引用。因此,克隆对象和原始对象共享了同一个对象成员变量,故而提出了深克隆 : 在对整个对象浅克隆后,还需对其引用变量进行克隆,并将其更新到浅克隆对象中去。
2、Clone() 方法的保护机制
在 Object 中 Clone() 是被申明为 protected 的,这样做是有一定的道理的,以 Employee 类为例,通过申明为 protected,就可以保证只有 Employee 类 (及其子类) 里面才能 "克隆"Employee 对象。
3、Clone() 方法的使用
Clone() 方法的使用比较简单,注意如下几点即可:
更多关于字面量的介绍请移步我的博文;
更多关于享元模式的介绍请移步我的博文。
来源: http://blog.csdn.net/justloveyou_/article/details/60983034