深入理解Java中的String转载
原创一、String类
要理解一个类,最好的方法是查看这个类的实现源代码并看一看。String类的源代码:
public final class String
implements java.io.Serializable, Comparable
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** 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;
........
}
综上所述,我们可以看到几点:
1)String类是final类,这意味着String类不能被继承,其成员方法默认final方法。在Java中,被final不允许继承修饰的类,并且类中的成员方法是默认的final方法。
2)以上所列String类中的所有成员属性,从上面可以看到。String课程实际上已经结束了。char用于保存字符串的数组。
我们继续来看一看String类的一些方法实现:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } char buf[] = new char[count + otherLen]; getChars(0, count, buf, 0); str.getChars(0, otherLen, buf, count); return new String(0, count + otherLen, buf); }
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = count; int i = -1; char[] val = value; / avoid getfield opcode / int off = offset; / avoid getfield opcode /
while (++i < len) {
if (val[off + i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0 ; j < i ; j++) {
buf[j] = val[off+j];
}
while (i < len) {
char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(0, len, buf);
}
}
return this;
}
从上面的三种方法可以看出,两者sub操、concat还是replace不会对原始字符串执行该操作,但会重新生成新的字符串对象。也就是说,在这些操作之后,原始字符串没有改变。
这里有一件事要永远记住: “String一旦对象被创建,它就是固定的。String对象中的任何更改不会影响原始对象、任何相关change该操作将生成一个新对象“ 。
2.字符串常数池
我们知道,像其他对象分配一样,字符串的分配需要花费大量的时间和空间,而且我们使用了大量的字符串。JVM为了提高性能和降低内存开销,实例化字符串时会做一些优化: 使用字符串常数池 。 每当我们创建字符串常量时,JVM首先检查字符串常量池,如果常量池中已经存在该字符串,则直接返回常量池中的实例引用。如果常量池中不存在该字符串,则实例化该字符串并将其放入常量池中。到期String弦的不变性,我们可以非常肯定,在常量池中不会有两个完全相同的弦。 (这对于理解上面的内容至关重要)。
Java中的常量池实际上分为两种形式: 静态恒定池 和 运行时间到时间池。 。
所谓 静态恒定池 ,即*.class文件中的常量池,class文件中的常量池包含的不仅仅是字符串。(数字)字面量,还包含有关类、方法、占用class文件空间的绝大部分。
而 运行时间到时间池。 ,则是jvm在虚拟机完成类加载操作后,它将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时间到时间池。。
让我们看一下下面的过程:
String a = "chenssy"; String b = "chenssy";
a、b而字面意思是chenssy都是指向JVM在字符串常量池中"chenssy"对象,则它们指向相同的对象。
String c = new String("chenssy");
new关键字必须生成对象。chenssy(注意这一点chenssy和上面的chenssy不同),而该对象存储在堆中。因此,上面应该会产生两个对象:保存在堆栈中。c并保存堆chenssy。但是在Java中没有两个完全相同的字符串对象。因此,在堆中chenssy应为引用字符串常量池。chenssy。所以c、chenssy、池chenssy这种关系应该是:c--->chenssy--->池chenssy。整个关系如下:
通过上面的数字,我们可以非常清楚地了解他们之间的关系。所以我们修改了内存中的值,他就改变了一切。
总结: 虽然a、b、c、chenssy是不同的对象,但来自String我们可以理解上述的内在结构。String c = new String("chenssy");虽然c内容是在堆中创建的,但他的内部value还是指向JVM常量池的chenssy的value,它构造chenssy使用的参数仍然是chenssy字符串常量。
让我们再看几个例子:
例子1:
/**
- 按文字值赋值 */ public void test1(){ String str1="aaa"; String str2="aaa"; System.out.println("===========test1============"); System.out.println(str1==str2);//true 可以看出str1跟str2指向相同的对象 }
执行上述代码将导致:true。
分析:在执行String str1="aaa"时,JVM首先转到字符串池,查看它是否存在。"aaa"如果该对象不存在,则在字符串池中创建该对象。"aaa"这个物体,然后是游泳池。"aaa"该对象的引用地址被返回给字符串常量。str1,这样str1会指向泳池"aaa"该字符串对象;"aaa"返回该对象的地址并将其分配给字符串常量。创建字符串对象时str2,则字符串池已存在。"aaa"这个对象,直接把对象放在"aaa"的参考地址str2,这样str2指向游泳池"aaa"这个对象,就是str1和str2指向同一对象,因此该语句System.out.println(str1 == str2)输出:true。
例子2:
/**
- 采用new关键字创建新的字符串对象 */ public void test2(){ String str3=new String("aaa"); String str4=new String("aaa"); System.out.println("===========test2============"); System.out.println(str3==str4);//false 由此可见,它的用途new方法是生成不同的对象。 }
执行上述代码将导致:false。
分析: 采用new关键字创建新的字符串对象时,JVM首先在字符串池中查找任何"aaa"此字符串对象(如果有)不是在池中创建的。"aaa"此对象,直接在堆中创建。"aaa"对象,然后将其放入堆中。"aaa"对象的地址被返回给引用。str3,这样,str3它指向在堆中创建的那个。"aaa"对象;如果不是,则首先在字符串池中创建一个。"aaa"对象,然后在堆中创建一个。"aaa"对象,然后将其放入堆中。"aaa"字符串对象的地址被返回到str3引用,这样,str3指向在堆中创建的"aaa"字符串对象。在执行时String str4=new String("aaa")时, 因为采用new当关键字创建对象时,每次。new所有出来的都是一个新对象,也就是引用。str3和str4指向两个不同的对象,因此该语句System.out.println(str3 == str4)输出:false。
例子3:
/**
- 编译周期确定 */ public void test3(){ String s0="helloworld"; String s1="helloworld"; String s2="hello"+"world"; System.out.println("===========test3============"); System.out.println(s0==s1); //true 可以看出s0跟s1指向相同的对象 System.out.println(s0==s2); //true 可以看出s0跟s2指向相同的对象 }
执行上述代码将导致:true、true。
分析:因为在示例中s0和s1中的"helloworld“是字符串常量,它们是在编译时确定的,因此。s0==s1为true;而"hello”和"world也是字符串常量,当一个字符串由多个字符串常量连接时,它肯定是字符串常量,所以。s2它还会在编译时解析为字符串常量,因此s2也是恒定池"helloworld“一个推荐人。所以我们来了。s0==s1==s2。
例子4:
/**
- 无法确定编译期间
*/
public void test4(){
String s0="helloworld";
String s1=new String("helloworld");
String s2="hello" + new String("world");
System.out.println("===========test4============");
System.out.println( s0==s1 ); //false
System.out.println( s0==s2 ); //false System.out.println( s1==s2 ); //false }
执行上述代码将导致:false、false、false。
分析:用new String() 创建的字符串不是常量,无法在编译时确定,因此new String() 创建的字符串不放在常量池中,它们有自己的地址空间。
s0或恒定池"helloworld“参考文献中,s1因为无法在编译周期确定,所以是运行时创建的新对象"helloworld“参考文献中,s2因为还有下半场new String(”world”)所以也无法在编译周期确定,所以也是一个新创建对象"helloworld“参考资料。
例子5:
/**
- 继续-无法确定编译期间
*/
public void test5(){
String str1="abc";
String str2="def";
String str3=str1+str2; System.out.println("===========test5============"); System.out.println(str3=="abcdef"); //false }
执行上述代码将导致:false。
分析:因为str3指向堆。"abcdef"对象,而"abcdef"是字符串池中的对象,因此结果是。false。JVM对String str="abc"放置在常量池中的对象在编译时完成,而String str3=str1+str2只有在操作的那一刻才知道。new对象也在运行时完成。这段代码创建了一个总的5对象,其中两个在字符串池中,三个在堆中。+操作符在堆中创建两个。String对象,则这两个对象的值为"abc"和"def"也就是说,从字符串池复制两个值,然后在堆中创建两个对象,然后创建对象。str3,然后将"abcdef"分配的堆地址的str3。
步骤:
1)在堆栈中打开一个中间存储引用。str1,str1指向池中String常量"abc"。
2)在堆栈中打开一个中间存储引用。str2,str2指向池中String常量"def"。
3)在堆栈中打开一个中间存储引用。str3。
4)str1 + str2通过StringBuilder最后一步toString()方法恢复了一个新的String对象"abcdef",因此在堆中打开一个空间来存储该对象。
5)引用str3指向堆中(str1 + str2)恢复的新版本String对象。
6)str3指向的对象在堆中,而常量。"abcdef"在池中,输出为false。
例子6:
/**
- 编译周期优化
*/
public void test6(){
String s0 = "a1";
String s1 = "a" + 1;
System.out.println("===========test6============");
System.out.println((s0 == s1)); //result = true
String s2 = "atrue"; String s3= "a" + "true"; System.out.println((s2 == s3)); //result = true
String s4 = "a3.4"; String s5 = "a" + 3.4; System.out.println((s4 == s5)); //result = true }
执行上述代码将导致:true、true、true。
分析:在程序编译过程中,JVM常量字符串为"+"连接优化是指连接后的值,取。"a" + 1例如,中的编译器优化后class已经是时候了a1。它的字符串常量的值是在编译时确定的,因此上面程序的最终结果是true。
例子7:
/**
- 无法确定编译期间 */ public void test7(){ String s0 = "ab"; String s1 = "b"; String s2 = "a" + s1; System.out.println("===========test7============"); System.out.println((s0 == s2)); //result = false }
执行上述代码将导致:false。
分析:JVM对于字符串引用,由于字符串。"+"在连接中,存在字符串引用,并且引用的值不能在程序的编译时确定,即。"a" + s1不能被编译器优化,只能动态地将连接的新地址分配和分配给程序。s2。因此,上述程序的结果是false。
例子8:
/**
- 比较字符串常量“+“和字符串引用”+”的区别 */ public void test8(){ String test="javalanguagespecification"; String str="java"; String str1="language"; String str2="specification"; System.out.println("===========test8============"); System.out.println(test == "java" + "language" + "specification"); System.out.println(test == str + str1 + str2); }
执行上述代码将导致:true、false。
分析:为什么会出现上述结果?这是因为字符串文字量拼接操作正在进行中。Java编译器在编译期间执行,这意味着编译器直接编译。"java"、"language"和"specification"这三个字面量被执行"+"操作将获得一个"javalanguagespecification" 常量,并将该常量直接放入字符串池,这样做实际上是一种优化,它将。3文字量被合成为1,避免了创建冗余的字符串对象。当字符串引用"+"运算是在Java在手术过程中进行的,即E.str + str2 + str3计算仅在程序执行期间执行,并且它在堆内存中重新创建拼接的字符串对象。总而言之,它是:字面数量"+"在编译时进行拼接,并将拼接后的字符串存储在字符串池中;"+"拼接操作实际上是在运行时执行的,新创建的字符串存储在堆中。
对于直接加法字符串,它是非常高效的,因为编译器确定它的值,即形状。"I"+"love"+"java"; 字符串是在编译期间添加和优化的。"Ilovejava"。对于间接加法(即,包含字符串引用),形状如下s1+s2+s3; 它的效率低于直接加法,因为编译器不会优化引用变量。
例子9:
/**
- 编译周期确定
*/
public void test9(){
String s0 = "ab";
final String s1 = "b";
String s2 = "a" + s1;
System.out.println("===========test9============"); System.out.println((s0 == s2)); //result = true }
执行上述代码将导致:true。
分析:和示例7唯一的区别是s1将添加该字符串final改装,final已修改的变量,在编译时解析为存储在其自身的常量池中或嵌入其字节流中的常量值的本地副本。所以在这个时候"a" + s1和"a" + "b"效果是一样的。因此,上述程序的结果是true。
例子10:
/**
- 无法确定编译期间 */ public void test10(){ String s0 = "ab"; final String s1 = getS1(); String s2 = "a" + s1; System.out.println("===========test10============"); System.out.println((s0 == s2)); //result = false
}
private static String getS1() {
return "b";
}
执行上述代码将导致:false。
分析:尽管这将是s1用final修饰,但因为它的赋值是通过方法调用返回的,所以只能在运行期间确定它的值。s0和s2上述程序的结果是false。
三、总结
1.String类在初始化后是不可变的。(immutable)
String使用private final char value[]来实现字符串的存储,即。String对象创建后,您不能再修改该对象中存储的字符串的内容,这就是为什么这么说的原因。String类型是不可变的(immutable)。程序员不能修改现有的不可变对象。我们也可以自己创建不可变的对象,只要没有办法修改接口中的数据。
然而,String类对象确实具有编辑字符串的能力,例如replace()。这些编辑功能是通过创建新对象而不是修改原始对象来实现的。例如:
s = s.replace("World", "Universe");
上面对s.replace()该调用将创建一个新字符串。"Hello Universe!"并返回对该对对象的引用。通过赋值、引用s将指向新字符串。如果没有其他引用指向原始字符串"Hello World!",则原始的字符串对象将被垃圾回收。
2.引用变量和对象。
A aa;
此语句声明一个类A参考变量aa[我们通常把它叫做把手。],而对象通常会通过。new创建。所以aa它只是一个引用变量,不是对象。
3.如何创建字符串
如何创建字符串归纳起来有两类:
(1)使用""报价创建字符串;
(2)使用new关键字创建一个字符串。
结合上面的例子,总结如下:
(1)单独使用""引号创建常量字符串。,已确定要存储编译期间。String Pool中;
(2)使用new String("")创建的对象存储在heap中,是在运行期间新创建的;
new在创建字符串时,首先检查池中是否有相同值的字符串,如果有,将一个复制到堆中,然后返回堆中的地址。如果不在池中,则在堆中创建副本,然后返回堆中的地址(请注意,此时不需要从堆复制到池中,否则,堆中的字符串将始终是!
(3使用仅包含常量的字符串连接器,例如。"aa" + "aa"Created也是常量,可以确定编译周期,已经确定了存储空间String Pool中;
(4使用包含以下变量的字符串连接器。"aa" + s1创建的对象仅在运行期间创建。,存储在heap中;
4.使用String不一定要创建对象
执行包含双引号中的字符串的语句时,例如。String a = "123",JVM它将首先查找常量池,如果有,它将在常量池中返回对此实例的引用,否则它将创建一个新实例并将其放置在常量池中。所以,当我们使用这样的。String str = "abc";在以的格式定义对象时,始终假定创建String类的对象str。 担心陷阱吧!该对象可能尚未创建!它可能只指向以前创建的对象。 只有通过new()方法以确保每次都创建一个新对象。
5.使用new String,必须创建一个对象
在执行String a = new String("123")首先,采用常量池的路径来获取对实例的引用,然后在堆上创建一个新的实例。String实例,则采用以下构造函数value属性分配,然后分配实例引用。a:
public String(String original) { int size = original.count; char[] originalValue = original.value; char[] v; if (originalValue.length > size) { // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array. int off = original.offset; v = Arrays.copyOfRange(originalValue, off, off+size); } else { // The array representing the String is the same // size as the String, so no point in making a copy. v = originalValue; } this.offset = 0; this.count = size; this.value = v; }
由此可见,虽然一个新的String实例,value等于常量池中的实例。value,那就是说不。new要存储的新字符数组。"123"。
6.关于String.intern()
intern方法用法: 最初为空的字符串池,由类组成。String独自维持。当呼叫时 intern如果池中已包含等于此值的。String对象的字符串(.equals(oject)方法OK),则返回池中的字符串。否则,这个String对象添加到池中,并返回此String对对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
String.intern();
还有一点:有.class在运行期间使用文件中的常量池。jvm加载,并可扩展。String的intern()该方法是扩大常量池的一种方式;String实例str调用intern()方法时,java找出常量池是否相同。unicode字符串常量,如果有,则返回其引用,如果没有,则向常量池中加一。unicode等于str并返回其引用。
/**
- 关于String.intern() */ public void test11(){ String s0 = "kvill"; String s1 = new String("kvill"); String s2 = new String("kvill"); System.out.println("===========test11============"); System.out.println( s0 == s1 ); //false System.out.println( "**" ); s1.intern(); //虽然执行了s1.intern(),但是它的返回值没有赋值。s1 s2 = s2.intern(); //把恒定池"kvill"引用s2 System.out.println( s0 == s1); //flase System.out.println( s0 == s1.intern() ); //true//说明s1.intern()返回常量池"kvill"的引用 System.out.println( s0 == s2 ); //true }
手术结果:false、false、true、true。
7.关于equals和==
(1)对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),然后比较其存储的"值"相等;如果作用于引用类型的变量(String),比较它所指向的对象的地址(即,它是否指向相同的对象)。
(2)equals方法是基类Object方法,所以对于所有继承的。Object类将具有此方法。在……里面Object类中,equals该方法用于比较两个对象的引用是否相等,即它们是否指向同一对象。
(3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有权利equals方法以比较引用类型的变量所指向的对象的地址;String类对equals方法以比较指定的字符串对象存储的字符串是否相等。其他类别,如Double,Date,Integer等,都对equals重写该方法以比较所指向的对象存储的内容是否相等。
/**
- 关于equals和== */ public void test12(){ String s1="hello"; String s2="hello"; String s3=new String("hello"); System.out.println("===========test12============"); System.out.println( s1 == s2); //true,表示s1和s2指向相同的对象,它们都指向常量池。"hello"对象 //flase,表示s1和s3地址不同,即E。它们指向不同的对象。,s1指向常量池中的地址,s3指向堆。地址 System.out.println( s1 == s3); System.out.println( s1.equals(s3)); //true,表示s1和s3指向的对象的内容是相同的。 }
8.String相关的+:
String中的 + 通常用于字符串。看看下面这个简单的例子:
/**
- String相关的+ */ public void test13(){ String a = "aa"; String b = "bb"; String c = "xx" + "yy " + a + "zz" + "mm" + b; System.out.println("===========test13============"); System.out.println(c); }
编译运行后,主字节码部分如下:
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
LDC "aa"
ASTORE 1
L1
LINENUMBER 6 L1
LDC "bb"
ASTORE 2
L2
LINENUMBER 7 L2
NEW java/lang/StringBuilder
DUP
LDC "xxyy "
INVOKESPECIAL java/lang/StringBuilder.
显然,我们可以从字节码中得出以下结论:
(1).String中使用 + 当字符串连接器连接到字符串时,如果所有连接操作在开头都是字符串常量,则编译后将尽可能直接连接字符串常量,形成新的字符串常量,以参与后续连接(通过反编译工具)。jd-gui它也可以很容易和直接地看到);
(2).下一个字符串连接从左到右。对于不同的字符串,首先使用最左侧的字符串作为参数创建最左侧的字符串。StringBuilder对象,然后依次向右转append行动,并最终将StringBuilder对象通过toString()方法转换String对象(注意:中间的多个字符串常量不会自动拼接)。
也就是说 String c = "xx" + "yy " + a + "zz" + "mm" + b; 实质性执行进程如下: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();
得出的结论是,当使用+当建立多个串连接时,实际上会产生一个串连接。StringBuilder对象和一个String对象。
9.String的不变性导致使用字符串变量。+这个数字的代价是:
String s = "a" + "b" + "c"; String s1 = "a"; String s2 = "b"; String s3 = "c"; String s4 = s1 + s2 + s3;
分析:变量s的创建是等价的 String s = "abc"; 从上面的例子可以看出,编译器已经过了优化,并且在这里只创建了一个对象。从上面的例子中也可以知道s4它不能在编译时优化,它的对象创建是等价的:
StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append(c);
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那就把它扔了。在下一个周期再次到达时重新生成StringBuilder对象,然后 append 字符串,因此循环到末尾。 如果我们直接领养 StringBuilder 对象进行 append 言归正传,我们可以拯救 N - 1 创建和销毁对象的时间到了。因此,对于希望在循环中连接字符串的应用程序,通常使用它们。StringBuffer或StringBulider要执行的对象append操作。
10.String、StringBuffer、StringBuilder的区别
(1)可变和不可变:String是 不可变的字符串对象 ,StringBuilder和StringBuffer是 变量字符串对象 (其内部字符数组的长度是可变的)。
(2)多线程安全性:String中的对象是不可变的,显然也可以理解为常量 线程安全 。StringBuffer 与 StringBuilder 中的方法和函数完全相同,只是。StringBuffer 中的大多数方法synchronized 关键字经过修饰,因此 线程安全 的,而 StringBuilder 如果没有这种修改,可以考虑 非线程安全性 的。
(3)String、StringBuilder、StringBuffer三个方面的实施效率:
StringBuilder > StringBuffer > String 当然,这是相对的,不一定在所有情况下都是如此。例如String str = "hello"+ "world"的效率 StringBuilder st = new StringBuilder().append("hello").append("world")很高。因此,这三个类别各有利弊,应根据不同情况使用:
建议添加字符串或进行较少更改。 String str="hello"本表格;
当字符串加法运算较多时,建议使用StringBuilder,如果使用了多线程。StringBuffer。
11.String中的final用法和理解
final StringBuffer a = new StringBuffer("111"); final StringBuffer b = new StringBuffer("222"); a=b;//这句话不是通过
final StringBuffer a = new StringBuffer("111"); a.append("222");//编译通过
可见, final仅适用于引用的"值"(也就是说,存储器地址)它强制引用仅指向最初指向的对象,并且更改其指向会导致编译时错误。 至于它所指向的对象的变化,final是不负责的。
12.关于String str = new String("abc")创建了多少对象?
这个问题在很多书中都有提到,比如.Java程序员面试书,包括国内很多大公司都会遇到的笔试试题,网上流传的大部分和一些面试书说2对象,则该语句是片面的。
首先,我们必须理解创建对象的意义。它是什么时候创建的?此代码是在运行时创建的。2一件物品?毫无疑问,它是不可能使用的javap -c反编译是可用的。JVM已执行的字节码内容:
很显然,new它只被调用了一次,也就是只创建了一个对象。这个主题令人困惑的地方在于,此代码在运行时确实只创建了一个对象,即在堆上。"abc"物体。为什么每个人都答应了2个对象呢,这里面要澄清一个概念,该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时间到时间池。中创建了一个"abc"对象,并且在代码执行期间只创建一个。String对象。
因此,如果替换此问题 String str = new String("abc")涉及到几个String反对?合理的解释是2个。
我个人认为,如果你在面试中遇到这个问题,你可以问面试官是不是创建了多少个对象,或者这个代码的执行涉及了多少个对象,然后根据具体的对象来回答。
13.字符串池的优缺点:
字符串池的优点是避免了创建相同内容的字符串,节省了内存,节省了创建相同字符串的时间,提高了性能;JVM遍历常量池中的对象所需的时间,但时间成本相对较低。
4.全面的例子
package com.spring.test;
public class StringTest {
public static void main(String[] args) {
/**
- 场景1:字符串池
- 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
/** - 场景2:关于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
} }
手术结果:
s1 == s2 : true
s1.equals(s2) : true
s3 == s4 : false
s3.equals(s4) : true
s1 == s3 : false
s1.equals(s3) : true
str1 = str11 : true
str4 = str5 : false
str7 = str67 : false
str9 = str89 : true
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除