`
小峰子
  • 浏览: 105667 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Java中的String

阅读更多

首先要知道,程序中的所有运行时数据都由JVM存储在内存中的:JVM会把内存,在逻辑上划分成不同的数据区来管理这些数据,并把相应的数据分别放在不同的数据区,有哪些数据区呢?
JVM包括这么些运行时数据区:方法区、栈区、堆区、 PC寄存器、本地方法栈。
程序中new出来的对象全放在堆区,而方法区中有个叫常量池的内存区,一些常量就放在里面,而像这种"hello"的String类型变量在JVM中是做为常量处理的。

String s = new String("abc");创建了几个String对象?

引用变量与对象的区别;
字符串文字"abc"是一个String对象;
文字池(pool of literal strings)和堆(heap)中的字符串对象。

一、引用变量与对象:除了一些早期的Java书籍和现在的垃圾书籍,人们都可以从中比较清楚地学习到两者的区别。
A aa;
这个语句声明一个类A的引用变量aa[我们常常称之为句柄],而对象一般通过new创建。所以题目中s仅仅是一个引用变量,它不是对象。

二、Java中所有的字符串文字[字符串常量]都是一个String的对象。有人[特别是C程序员]在一些场合喜欢把字符串"当作/看成"字符数组,这也没有办法,因为字符串与字符数组存在一些内在的联系。事实上,它与字符数组是两种完全不同的对象。

System.out.println("Hello".length());
char[] cc={'H','i'};
System.out.println(cc.length);

三、字符串对象的创建:
由于字符串对象的大量使用(它是一个对象,一般而言对象总是在heap分配内存)Java中为了节省内存空间和运行时间(如比较字符串时,==equals()),在编译阶段就把所有的字符串文字放到一个文字池(pool of literal strings)中,而运行时文字池成为常量池的一部分。文字池的好处,就是该池中所有相同的字符串常量被合并,只占用一个空间。
我们知道,对两个引用变量,使用==判断它们的值(引用)是否相等,即指向同一个对象:

String s1 = "abc" ;
String s2 = "abc" ;
if( s1 == s2 ) System.out.println("s1,s2 refer to the same object");
else System.out.println("trouble");

这里的输出显示,两个字符串文字保存为一个对象。就是说,上面的代码只在pool中创建了一个String对象。

现在看String s = new String("abc");语句,这里"abc"本身就是pool中的一个对象,而在运行时执行new String()时,
pool中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s持有。ok,这条语句就创建了2String对象。

String s1 = new String("abc") ;
String s2 = new String("abc") ;
if( s1 == s2 ){ //
不会执行的语句}

这时用==判断就可知,虽然两个对象的"内容"相同(equals()判断),但两个引用变量所持有的引用不同,
上面的代码创建了几个String Object? (三个,pool中一个,heap2个。)

wuwu总结

综上所述,创建字符串有两种方式:两种内存区域(pool,heap

1" " 引号创建的字符串在字符串池中

2newnew创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址(注意,此时不需要从堆中复制到池中,否则,将使得堆中的字符串永远是池中的子集,导致浪费池的空间)!

另外,对字符串进行赋值时,如果右操作数含有一个或一个以上的字符串引用时,则在堆中再建立一个字符串对象,返回引用;如String s=str1+ "blog";
比较两个已经存在于字符串池中字符串对象可以用"=="进行,拥有比equals操作符更快的速度

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Java面试中总喜欢出几个关于String对象的题,比如:

 

1.String s = new String("abc");创建了几个String对象。

2.String s1 = "abc";
   String s2 = "a";
   String s3 = s2 + "bc";
   String s4 = "a" + "bc";
   String s5 = s3.intern();
请问s1==s3是true还是false,s1==s4是false还是true。s1==s5呢?

这样的题碰到很多了,今天来全面的总结一下,之所以敢说全面,是因为我参考的是最权威的资料……Java语言规范和Java API。

API中说明:

字符串是常量;它们的值在创建之后不能更改。因为 String 对象是不可变的,所以可以共享。(http://gceclub.sun.com.cn/Java_Docs/jdk6/html/zh_CN/api/java/lang/String.html)

那么,是如何共享的勒,String类的intern方法有如下说明:

字符串池,初始为空,它由类 String 私有地维护。(http://gceclub.sun.com.cn/Java_Docs/jdk6/html/zh_CN/api/java/lang/String.html#intern())

也就是说,String类自己维护了一个池,把程序中的String对象放到那个池里面,于是我们代码中只要用到值相同的String,就是同一个String对象,节省了空间。

但是,并不是任何时候任何String对象都在这个字符串池中,不然也就不会有那些面试题了。那么,到底哪些String对象在池中,哪些在堆中?请看Java语言规范。

Java语言规范第三版,第3.10.5小节,String Literals的最后一段如下:

 

Literal strings within the same class (§8) in the same package (§7) represent references to the same String object (§4.3.1).

Literal strings within different classes in the same package represent references to the same String object.

Literal strings within different classes in different packages likewise represent references to the same String object.

Strings computed by constant expressions (§15.28) are computed at compile time and then treated as if they were literals.

Strings computed by concatenation at run time are newly created and therefore distinct.

The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.

以上6句话就是权威且全面的解释了。我依次解释下。

首先解释下什么是字符串字面常数(String Literals),字面常数(Literals)就是你写在源代码里面的值,比如说int i = 6; 6就是一个整数形字面常数。String s = "abc"; “abc”就是一个字符串字面常数。Java中,所有的字符串字面常数都放在上文提到的字符串池里面,是可以共享的,就是说,String s1 = "abc"; String s2 = "abc"; s1,s2都引用的同一个字符串对象,而且这个对象在字符串池里面,因此s1==s2。另外,字符串字面常数是什么时候实例化并放到字符串池里面去的呢?答案是Load Class的时候(Java Spec 12.5)。

下面看那6句话。

前面三句基本废话,想绕口令一样,意思就是说任何类任何包,值相同的字符串字面常数(String Literals)都引用同一个对象。

第四句是说,通过常量表达式(constant expressions)计算出来的字符串,也算字符串字面常数,就是说他们也在字符串池中。什么是常量表达式(constant expressions)待会说,这个很重要。

注意第五句话,在程序运行时通过连接(+)计算出来的字符串对象,是新创建的,他们不是字面常数,就算他们值相同,他们也不在字符串池里面,他们在堆内存空间里,因此引用的对象各不相同。

最后一句话也很重要,String类的intern方法,返回一个值相同的String对象,但是这个对象就像一个字符串字面常数一样,意思就是,他也到字符串池里面去了。

现在我们来看开头的两个题目。

 

1.String s = new String("abc");创建了几个String对象。

答案是2个,一个是字符串字面常数,在字符串池中。一个是new出来的字符串对象,在堆中。

2.String s1 = "abc";
   String s2 = "a";
   String s3 = s2 + "bc";
   String s4 = "a" + "bc";
   String s5 = s3.intern();
请问s1==s3是true还是false,s1==s4是false还是true。s1==s5呢?

此题注意两点,因为s2是一个变量,所以s3是运行时才能计算出来的字符串,是new的,在堆中不在字符串池中。s4是通过常量表达式计算出来的,他等同于字符串字面常数,在字符串池中。所以,s1!=s3,s1==s4。再看s5,s5是s3放到字符串池里面返回的对像,所以s1==s5。这里新手要注意的是,s3.intern()方法,是返回字符串在池中的引用,并不会改变s3这个变量的引用,就是s3还是指向堆中的那个"abc",并没有因调用了intern()方法而改变,实际上也不可能改变。

好,现在我们回到前文没有说清楚的一个问题,到底什么算常量表达式(constant expressions)。上面提到了两种非常量表达式,new 和变量相加。至于什么算常量表达式(constant expressions),请看Java语言规范。

 

15.28 Constant Expression

 ConstantExpression:         Expression

A compile-time constant expression is an expression denoting a value of primitive type or a String that does not complete abruptly and is composed using only the following:

  • Literals of primitive type and literals of type String 
  • Casts to primitive types and casts to type String
  • The unary operators +-~, and (but not ++ or --)
  • The multiplicative operators */, and %
  • The additive operators + and -
  • The shift operators <<>>, and >>>
  • The relational operators <<=>, and >= (but not instanceof)
  • The equality operators == and !=
  • The bitwise and logical operators &^, and |
  • The conditional-and operator && and the conditional-or operator ||
  • The ternary conditional operator ? :
  • Parenthesized expressions whose contained expression is a constant expression.
  • Simple names that refer to constant variables
  • Qualified names of the form TypeName . Identifier that refer to constant variables

Compile-time constant expressions are used in case labels in switch statements and have a special significance for assignment conversion. Compile-time constants of type String are always "interned" so as to share unique instances, using the method String.intern.

A compile-time constant expression is always treated as FP-strict, even if it occurs in a context where a non-constant expression would not be considered to be FP-strict.

看着是不是有点晕,这情况也太多了,面试应该不会那么变态的。其实看着复杂,记住一个原则就好了,那就是编译时能确定的,就算,运行时才能确定的,就不算。以下为例子

String s = 1 + "23";//算,符合第二条Casts to primitive types and casts to type String
String s = (2 > 1) + "" ;//算,意味着s=="true",且这个“true”已经放到字符串池里面去了。
String s = (o instanceof Object) + "";//不算,instanceof这个操作符决定了不算。s=="true",但这个"true"对象在堆中。

留意下面的情况。

   final String s2 = "a";
   String s3 = s2 + "bc";//算

注意现在的s2+"bc"也算一个常量表达式,理由是那个列表里面的最后两条,s2是一个常量变量(constant variables),问题又来了,什么是常量变量?规范里也说了,被final修饰,并且通过常量表达式初始化的变量,就是常量变量。变量s2被final修饰,他通过常量表达式"a"初始化,所以s2是一个常量变量,所以s3引用的"abc",也在字符串池里面咯,明白了吧。

再举个反例:

final String s2 = getA();
public String getA(){return "a"}

这是时候的s2,就不是常量变量了哦,因为getA()不是一个常量表达式。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics