`

Java中堆栈问题,对象比较问题 文章搜集

    博客分类:
  • Java
 
阅读更多

1常量池:

 

在JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,因此它提高了效率。String a="abc";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"abc"的这么一个对象,判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。

 

2 字符串对象的创建:

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

 

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

 

  String s1 = new String("abc") ;String s2 = new String("abc") ;if( s1 == s2 ){ }//false

 

  //创建了几个String Object? [三个,pool中一个"abc",heap中2个。]

 

只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。

 

 

 

另外

 

1.==表示引用自同一对象,equals()表示值相等。

 

String str1 = "abc";引用的对象在栈(或者叫String池)中。

String str1 =new String ("abc"); 引用的对象在内存/堆中。

 

2.String str1 =  "string";在栈中

 

  String str3 =  "str";在栈中

 

  String str4 = "ing";在栈中

 

  String str2 = str3+str4; 在堆中,因为+号的作用是返回另外一个新建的String对象,而不是在栈中找string这个值。

 

String str2 = "str"+"ing";在栈中。str1==str2为true。

 

 

 

但是有一种情况需要引起我们的注意。请看下面的代码:

 

public class StringStaticTest {      

 

    public static final String A = "ab"; // 常量A

 

    public static final String B = "cd"; // 常量B

 

    public static void main(String[] args) {

 

         String s = A + B;  // 将两个常量用+连接对s进行初始化 

 

         String t = "abcd";   

 

        if (s == t) {   

 

             System.out.println("s等于t,它们是同一个对象");   

 

         } else {   

 

             System.out.println("s不等于t,它们不是同一个对象");   

 

         }   

 

     }   

 

}  

 

这段代码的运行结果如下:

 

s等于t,它们是同一个对象

 

原因是在上面的例子中,A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B;  等同于:String s="ab"+"cd";

 

我对上面的例子稍加改变看看会出现什么情况:

 

public class StringStaticTest {       

 

    public static final String A; // 常量A

 

    public static final String B;    // 常量B

 

    static {   

 

         A = "ab";   

 

         B = "cd";   

 

     }   

 

     public static void main(String[] args) {   

 

        // 将两个常量用+连接对s进行初始化   

 

         String s = A + B;   

 

         String t = "abcd";   

 

        if (s == t) {   

 

             System.out.println("s等于t,它们是同一个对象");   

 

         } else {   

 

             System.out.println("s不等于t,它们不是同一个对象");   

 

         }   

 

     }   

 

}

 

它的运行结果是这样:

 

s不等于t,它们不是同一个对象

 

只是做了一点改动,结果就和刚刚的例子恰好相反。我们再来分析一下。A和B虽然被定义为常量(只能被赋值一次),但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

 

最后我们再来说说String对象在JAVA虚拟机(JVM)中的存储,以及字符串池与堆(heap)和栈(stack)的关系。我们首先回顾一下堆和栈的区别:

 

栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。

 

堆(heap):用于存储对象。

 

我们查看String类的源码就会发现,它有一个value属性,保存着String对象的值,类型是char[],这也正说明了字符串就是字符的序列。当执行String a="abc";时,JAVA虚拟机会在栈中创建三个char型的值'a'、'b'和'c',然后在堆中创建一个String对象,它的值(value)是刚才在栈中创建的三个char型值组成的数组{'a','b','c'},最后这个新创建的String对象会被添加到字符串池中。

 

如果我们接着执行String b=new String("abc");代码,由于"abc"已经被创建并保存于字符串池中,因此JAVA虚拟机只会在堆中新创建一个String对象,但是它的值(value)是共享前一行代码执行时在栈中创建的三个char型值值'a'、'b'和'c'。

 

 

 

      String s0=”kvill”; 

 

 

  String s1=”kvill”; 

 

  String s2=”kv” + “ill”; 

 

  System.out.println( s0==s1 ); 

 

  System.out.println( s0==s2 ); 

 

 

String s1 = "a"; String s2 = "b"; String s3 = s1+s2 +"c";

四个对象

 

 

另外:

 

字符串的+运算和字符串转换 

 

  字符串转换和串接是很基础的内容,因此我以为这个问题简直就是送分题。事实上,我自己就答错了。 

 

  String str = new String("jf"); // jf是接分 

 

  str = 1+2+str+3+4; 

 

  一共创建了多少String的对象?[我开始的答案:5个。jf、new、3jf、3jf3、3jf34] 

 

  首先看JLS的有关论述: 

 

  一、字符串转换的环境[JLS 5.4 String Conversion] 

 

  字符串转换环境仅仅指使用双元的+运算符的情况,其中一个操作数是一个String对象。在这一特定情形下,另一操作数转换成String,表达式的结果是这两个String的串接。 

 

  二、串接运算符[JLS 15.18.1 String Concatenation Operator + ] 

 

  如果一个操作数/表达式是String类型,则另一个操作数在运行时转换成一个String对象,并两者串接。此时,任何类型都可以转换成String。[这里,我漏掉了"3"和"4"] 

 

  如果是基本数据类型,则如同首先转换成其包装类对象,如int x视为转换成Integer(x)。 

 

  现在就全部统一到引用类型向String的转换了。这种转换如同[as if]调用该对象的无参数toString方法。[如果是null则转换成"null"]。因为toString方法在Object中定义,故所有的类都有该方法,而且Boolean, Character, Integer, Long, Float, Double, and String改写了该方法。 

 

  关于+是串接还是加法,由操作数决定。1+2+str+3+4 就很容易知道是"3jf34"。[BTW :在JLS的15.18.1.3中举的一个jocular little example,真的很无趣。] 

 

  下面的例子测试了改写toString方法的情况.。 

 

  class A 

 

  { int i = 10; 

 

  public static void main(String []args) 

 

  { String str = new String("jf"); 

 

  str += new A(); 

 

  System.out.print(str); 

 

  } 

 

  public String toString(){ return " a.i ="+i+"\n"; } 

 

  } 

 

  三、字符串转换的优化 

 

  按照上述说法,str = 1+2+str+3+4;语句似乎应该就应该生成5个String对象: 

 

  1+2 =3,then 3→Integer(3)→"3" in pool? [假设如此] 

 

  "3"+str(in heap) = "3jf" (in heap) 

 

  "3jf" +3 ,first 3→Integer(3)→"3" in pool? [则不创建] then "3jf3" 

 

  "3jf3"+4 create "4" in pool 

 

  then "3jf34" 

 

  这里我并不清楚3、4转换成字符串后是否在池中,所以上述结果仍然是猜测。 

 

  为了减少创建中间过渡性的字符串对象,提高反复进行串接运算时的性能,a Java compiler可以使用StringBuffer或者类似的技术,或者把转换与串接合并成一步。例如:对于 a + b + c ,Java编译器就可以将它视为[as if] 

 

  new StringBuffer().append(a).append(b).append(c).toString(); 

 

  注意,对于基本类型和引用类型,在append(a)过程中仍然要先将参数转换,从这个观点看,str = 1+2+str+3+4;创建的字符串可能是"3"、"4"和"3jf34"[以及一个StringBuffer对象]。 

 

  现在我仍然不知道怎么回答str = 1+2+str+3+4;创建了多少String的对象,。或许,这个问题不需要过于研究,至少SCJP不会考它。 

 

  3、这又不同:str = "3"+"jf"+"3"+"4"; 

 

  如果是一个完全由字符串文字组成的表达式,则在编译时,已经被优化而不会在运行时创建中间字符串。测试代码如下: 

 

  String str1 ="3jf34"; 

 

  String str2 ="3"+"jf"+"3"+"4"; 

 

  if(str1 == str2) { System.out.println("str1 == str2"); } 

 

  else { System.out.println("think again"); } 

 

  if(str2.equals(str1)) System.out.println("yet str2.equals(str1)"); 

 

  可见,str1与str2指向同一个对象,这个对象在pool中。所有遵循Java Language Spec的编译器都必须在编译时对constant expressions 进行简化。JLS规定:Strings computed by constant expressions (?15.28) are computed at compile time and then treated as if they were literals. 

 

  对于String str2 ="3"+"jf"+"3"+"4";我们说仅仅创建一个对象。注意,“创建多少对象”的讨论是说运行时创建多少对象。 

 

  BTW:编译时优化 

 

  String x = "aaa " + "bbb "; 

 

  if (false) { x = x + "ccc "; } 

 

  x += "ddd "; 

 

  等价于: String x = "aaa bbb "; x = x + "ddd "; 

分享到:
评论

相关推荐

    JAVA错误收集

    标题“JAVA错误收集”指的是Java编程中遇到的异常和错误的汇总,这通常涉及到程序运行时的错误处理和调试过程。在Java中,错误(Error)是程序无法处理的严重问题,比如系统资源耗尽或者硬件故障,而异常(Exception...

    java内存溢出原因

    - **heap space** 主要用于存储Java运行时的对象,这些对象会在垃圾收集(GC)过程中被清理。 - **错误日志**:`java.lang.OutOfMemoryError: Java heap space` 和 `java.lang.OutOfMemoryError: GC overhead ...

    javacore\heapdump文件分析工具

    而`heapdump`文件则是JVM在运行过程中,通过Java的`jmap`命令或者`VisualVM`等工具手动触发生成的,它记录了JVM堆内存的详细状态,包括对象实例、类加载器、垃圾收集信息等。`ha`(Heap Analysis)工具则用于分析`...

    Java进程cpu占用率高

    在Java编程环境中,当遇到“Java进程CPU占用率高”的问题时,这通常意味着Java应用程序在执行过程中消耗了大量计算资源,可能导致系统响应变慢,甚至出现性能瓶颈。本篇文章将深入探讨这个问题,并提供相关解决方案...

    分布式JAVA应用+基础与实践

    本篇文章将深入探讨分布式Java应用的基础知识和实践技巧,帮助开发者理解和掌握分布式系统的核心概念。 1. 分布式系统概述 分布式系统是由多个独立的计算机节点通过网络连接,共同协作完成一项任务。在Java中,主要...

    毕业设计论文-IT计算机-[新闻文章]自动新闻采集系统_webapps-源码.zip

    1. **JAVA后台开发**:JAVA因其面向对象的特性、强大的类库支持和跨平台能力,在Web应用开发中广泛应用。在自动新闻采集系统中,JAVA用于编写服务器端逻辑,处理HTTP请求,与数据库交互,进行数据处理和计算。通常会...

    visiualvm 堆dump分析工具

    在本篇文章中,我们将深入探讨如何利用VisualVM进行堆dump分析,以及它在处理"jvm.gc"(Java垃圾收集)问题上的应用。 首先,让我们了解什么是堆dump。在Java应用程序运行过程中,堆内存是用于存储对象的主要区域。...

    jsr80 java 访问 usb

    usb.devices : 这个可选包收集了用 jUSB API 访问不同 USB 设备的 Java 代码,包括柯达数码相机和 Rio 500 MP3 播放器。这些 API 经过特别编写以简化访问特定 USB 设备的过程,并且不能用于访问其他设备。这些 API ...

    线上问题调查常用命令

    本篇文章将基于提供的文件信息,深入解析线上问题调查中常用的Linux性能检测工具及JVM性能相关命令,并提供详细的解释与应用场景。 #### Linux性能检测工具 **1. CPU性能检测** ##### 基本概念 - **上下文切换**...

    JVM下篇:性能监控与调优篇.7z

    - **堆内存**:Java对象主要存储在堆内存中,分为新生代(Young Generation)和老年代(Tenured Generation)。新生代用于存放新生的对象,通过Minor GC进行垃圾回收;老年代存放长期存活的对象,通过Major GC或...

    twitter上发现了<jQuery Performance Rules>这篇文章,

    4. **内存分析**: 分析内存分配,查找内存泄漏,通过对象分配视图和垃圾收集日志来优化内存管理。 5. **线程分析**: 查看线程状态,定位死锁和阻塞,帮助解决多线程问题。 6. **数据库监控**: 检查SQL语句的执行...

    jvm常用命令工具

    在这个例子中,程序创建了大量的1MB大小的对象,并将其添加到列表中。通过使用上述工具,我们可以监测程序在运行过程中的内存消耗情况、线程活动情况以及垃圾回收行为等。 ### 结论 通过本文介绍的JVM命令工具,...

    博客系统项目源代码下载

    - 监控与日志:通过Prometheus、Grafana等工具监控系统性能,利用Logstash、ELK堆栈收集分析日志。 8. **持续集成/持续部署(CI/CD)** - Jenkins、GitLab CI/CD等工具,实现自动化测试、构建和部署,确保代码质量...

    微服务+DDD代码结构例子-spring-microservice-ddd.zip

    10. **监控与日志**:集成Spring Boot Actuator和ELK(Elasticsearch、Logstash、Kibana)堆栈,实现微服务的性能监控和日志收集分析。 通过上述步骤,我们可以构建一个基于Spring的微服务系统,该系统充分利用了...

    使用open source产品组装你的web应用架构(转载)

    10. **监控与日志**:Prometheus、Grafana用于系统性能监控,ELK (Elasticsearch, Logstash, Kibana)堆栈用于日志收集和分析,帮助开发者及时发现和解决问题。 11. **安全**:开源安全库如OAuth2、JWT(JSON Web ...

    RoyalWebStore:后端

    通过ELK(Elasticsearch、Logstash、Kibana)堆栈收集和分析应用日志,配合Prometheus和Grafana进行性能监控,有助于快速定位问题和优化系统。 10. **测试策略** 使用JUnit进行单元测试,Mockito模拟外部依赖,...

Global site tag (gtag.js) - Google Analytics