• Java String对象的经典问题(转)


    public class StringTest {
    	public static void main(String[] args)
    	{
    		String strA = "abc";
    		String strB = "abc";
    		String strC = new String("abc");
    		
    		System.out.println(strA == strB);//true
    		System.out.println(strA == "abc");//true
    		System.out.println(strC == "abc");//false		
    		System.out.println(strA == strC);//false
    		
    		System.out.println(strA.equals(strC));//true
    		System.out.println(strB.equals(strC));//true
    		
    		System.out.println(strC.intern());//abc
    		System.out.println(strA == strC.intern());//true
    		
    		System.out.println(strA.hashCode());//96354
    		System.out.println(strA.hashCode() == strC.hashCode());//true
    		
    	}
    
    }
    

      做个基础的说明,堆(heap)内存和栈(Stack)内存的问题。堆和栈的数据结构这里就不解释了。Java语言使用内存的时候,栈内存主要保存以下内容:基本数据类型和对象的引用,而堆内存存储对象,栈内存的速度要快于堆内存。总结成一句话就是:引用在栈而对象在堆。 
        Java中的比较有两种,是==(判断对象引用是否相同)和equals()方法,equals()是Object类的方法,定义在Object类中的equals()方法是如下实现的:

    public boolean equals (object obj)
    {
         return (this == obj);       
    }
    

       String类重写了equals()方法,改变了这些类型对象相等的原则,即判断对象是否相等依据的原则为判断二者的内容是否相等。 
        了解以上内容后我们来说说String,String类的本质是字符数组char[],其次String类是final的,是不可被继承的,这点可能被大多数人忽略,再次String是特殊的封装类型,使用String时可以直接赋值,也可以用new来创建对象,但是这二者的实现机制是不同的。还有一个String池的概念,Java运行时维护一个String池,池中的String对象不可重复,没有创建,有则作罢。String池不属于堆和栈,而是属于常量池(应该位于方法区)。

      下面分析上方代码的真正含义 

    String strA = "abc";
    String strB = "abc";
    

      第一句的真正含义是在String池中创建一个对象”abc”,然后引用时strA指向池中的对象”abc”。第二句执行时,因为”abc”已经存在于String池了,所以不再创建,则strA==strB返回true就明白了。strB==”abc”肯定正确了,在String池中只有一个”abc”,而strA和strB都指向池中的”abc”,就是这个道理。

    String strC = new String("abc");
    

       这个是Java SE的热点问题,众所周知,单独这句话创建了2个String对象,而基于上面语句,只在栈内存创建strC引用,在堆内存上创建一个String对象,内容是”abc”,而strC指向堆内存对象的首地址。 
        下面就是strC==”abc”的问题了,显然不对,”abc”是位于String池中的对象,而str2指向的是堆内存的String对象,==判断的是地址,肯定不等了。 
        strA.equals(strC),这个是对的,前面说过,String类的equals重写了Object类的equals()方法,实际就是判断内容是否相同了。 
        下面说下intern()方法,在JavaDoc文档中,这样描述了intern()方法:返回字符串对象的规范化表示形式。怎么理解这句话?实际上过程是这样进行的:该方法现在String池中查找是否存在一个对象,存在了就返回String池中对象的引用。 
        那么本例中String池存在”abc”,则调用intern()方法时返回的是池中”abc”对象引用,那么和strA/strB都是等同的,和strC就不同了,因为strC指向的是堆内存。 
        hashCode()方法是返回字符串内容的哈希码,既然内容相同,哈希码必然相同,那他们就相等了,这个容易理解。

    再看下面例子 

    public class Test {
    	private static String str = "abc";
    	public static void main(String[] args) {
    		String str1 = "a";
    		String str2 = "bc";
    		String combo = str1 + str2;
    		System.out.println(str == combo);//false
    		System.out.println(str == combo.intern());//true
    	}
    }
    

      这个例子用来说明用+连接字符串时,实际上是在堆内容创建对象,那么combo指向的是堆内存存储”abc”字符串的空间首地址,显然str==combo是错误的,而str==combo.intern()是正确的,在String池中也存在”abc”,那就直接返回了,而str也是指向String池中的”abc”对象的。此例说明任何重新修改String都是重新分配内存空间,这就使得String对象之间互不干扰。也就是String中的内容一旦生成不可改变,直至生成新的对象。 
        同时问题也来了,使用+连接字符串每次都生成新的对象,而且是在堆内存上进行,而堆内存速度比较慢(相对而言),那么再大量连接字符串时直接+是不可取的,当然需要一种效率高的方法。Java提供的StringBuffer和StringBuilder就是解决这个问题的。区别是前者是线程安全的而后者是非线程安全的,StringBuilder在JDK1.5之后才有。不保证安全的StringBuilder有比StringBuffer更高的效率。 
        自JDK1.5之后,Java虚拟机执行字符串的+操作时,内部实现也是StringBuilder,之前采用StringBuffer实现。 

    转自:http://sarin.iteye.com/blog/603684/

    java常量池技术  
    java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,常量池存在于方法区中。
     
     
    JVM的编译器将源程序编译成class文件后,会用一部分字节分类存储这些粗体代码。而这些字节我们就称为常量池。其中包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s ="java"这种申明方式;对于String s = "java",在编译成.class时能够识别为同一字符串的,自动优化成常量,所以如果有多个字符串"java",则它们都会引用自同一String对象。也就是说String s = "java" 其中"java"值在JAVA程序编译期就确定下来了的。(大家可以用UE编辑器或其它文本编辑工具在打开class文件后的字节码文件中看到这个java值)。这个java存在在常量池中。注意:常量池只存储文字字符串值,不存储符号引用。
     
    而在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回一个引用,没有则添加自己的字符串进入常量池,注意:只是字符串部分。所以这时会存在2份拷贝,常量池的部分被String类私有并管理,自己的那份按对象生命周期继续使用。String s = new String("java");语句,到底创建了几个对象呢?  "java"本身就是常量池中的一个对象,而在运行时执行new String()时,将常量池中的对象复制一份放到堆中,并且把堆中的这个对象的引用交给s持有。所以这条语句就创建了2个String对象。
     
     
    String类也是java中用得多的类,同样为了创建String对象的方便,也实现了常量池的技术。
    测试代码如下:
    public class Test
    {
    public static void main(String[] args)
    {
    //s1,s2分别位于栈中不同空间
    String s1=new String("hello");
    String s2=new String("hello");
    System.out.println(s1==s2);//输出false
    //s3,s4位于池中同一空间
    String s3="hello" Strings4="hello";
    System.out.println(s3==s4);//输出true
    }
    }
    

      

    用new String()创建的字符串不是常量,不能在编译期就确定,所以new String()创建的字符串不放入常量池中,他们有自己的地址空间。
    String 对象(内存)的不变性机制会使修改String字符串时,产生大量的对象,因为每次改变字符串,都会生成一个新的String。 java 为了更有效的使用内存,常量池在编译期遇见String 字符串时,它会检查该池内是否已经存在相同的String 字符串,如果找到,就把新变量的引用指向现有的字符串对象,不创建任何新的String 常量对象,没找到再创建新的。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。
    代码:
    String a = “test”;
    String b = “test”;
    String b = b+"java";
    a,b同时指向常量池中的常量值"text",b=b+"java"之后,b原先指向一个常量,内容为"test”,通过对b进行+"java"操作后,b之前所指向的那个值没有改变,但此时b不指向原来那个变量值了,而指向了另一个String变量,内容为”text java“。原来那个变量还存在于内存之中,只是b这个变量不再指向它了。
     
     
    八种基本类型的包装类和对象池  java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用常量池
    Integer a = new Integer(128);
    Integer b = new Integer(128);
    这个时候再问你,输出结果是什么?你就知道是false了。如果把这个数换成127,再执行:
    Integer a = 127;
    Integer b = 127;
    System.out.println(a == b);
    结果就是:true
    进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。这里引出equals()和==,equals比较的是字符串字面值即比较内容,==比较引用。
    转自 : http://www.cnblogs.com/dapeng111/p/3530542.html
     
     

    实例代码

     public static void main(String args[])  
        {  
            String s1 = "a";  
            String s2 = "b";  
            String s3 = "ab";  
              
            String s4 = "ab";  
            System.out.println("s3==s4? "+ (s3==s4));  
      
            String s5 = "a"+"b";  
            System.out.println("s3==s5? "+ (s3==s5));  
              
            String s6 = s1+s2;  
            System.out.println("s3==s6? "+ (s3==s6));  
              
            String s7 = new String("ab");  
            System.out.println("s3==s7? "+ (s3==s7));  
              
            final String s8 = "a" ;   
            final String s9 = "b" ;  
            String s10 = s8 + s9;  
            System.out.println("s3==s10? "+ (s3==s10));  
            }  
    

      上述代码解析:

    s3与s4根据String的概念他们都指向了同一个缓冲池内的地址,所以结果为true 

    s3与s5因为相加的两个为常量所以编译器会把s5="a"+"b"优化为s5="ab"。所以结果也为true。 

    s3与s6因为是两个变量的相加所以编译器无法优化,s1+s2即等同于(new StringBuilder(String.valueOf(s1))).append(s2).toString(); 在运行时,会有新的String地址空间的分配,而不是指向缓冲池中的“ab”。所以结果false。 

    s3与s7,根据缓冲池的定义在new的时候实际会新分配地址空间,s7指向的是新分配的地址空间所以与缓冲池地址不同,所以为false   

    s3与s10,类似于s3与s5,因为是final类型编译器进行了优化所以相同。 
     

    输出结果为:  

    s3==s4? true
    s3==s5? true
    s3==s6? false
    s3==s7? false
    s3==s10? true
    

      

    创建字符串的方式很多,归纳起来有三类:  

    其一,使用new关键字创建字符串,比如String s1 = new String("abc"); 

    其二,直接指定。比如String s2 = "abc"; 

    其三,使用串联生成新的字符串。比如String s3 = "ab" + "c"; 

    String对象的创建 

    String对象的创建也很讲究,关键是要明白其原理。   

    原理1:当使用任何方式来创建一个字符串对象s时,Java运行时(运行中JVM)会拿着这个X在String池中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串s,否则,不在池中添加。 
       
    原理2:Java中,只要使用new关键字来创建对象,则一定会(在堆区或栈区)创建一个新的对象。 
      
    原理3:使用直接指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String池中的字符串,池中没有就在池中创建一个,有则罢了!但绝不会在堆栈区再去创建该String对象。  
      
    原理4:使用包含变量的表达式来创建String对象,则不仅会检查维护String池,而且还会在堆栈区创建一个String对象。 

    转自 : 网络

  • 相关阅读:
    强化学习基础:蒙特卡罗和时序差分
    强化学习基础:基本概念和动态规划
    EM算法和高斯混合模型GMM介绍
    使用Tensorflow搭建自编码器(Autoencoder)
    神经网络介绍:梯度下降和正则化
    神经网络介绍:算法基础
    K均值聚类和DBSCAN介绍
    Zabbix安装过程中遇到的问题
    Docker容器操作基础命令(二)
    Docker的安装及基本用法(一)
  • 原文地址:https://www.cnblogs.com/whytohow/p/4847565.html
Copyright © 2020-2023  润新知