`
lingangw
  • 浏览: 10808 次
  • 性别: Icon_minigender_1
  • 来自: 成都
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

String 纷争解释(转载)

阅读更多

本文转自:

★ 镜头总结: String类型脱光了其实也很普通。真正让她神秘的原因就在于CONSTANT_String_info常量表 拘留字符串对象 的存在。现在我们可以解决江湖上的许多纷争了。

  纷争1】关于字符串相等关系的争论

Java代码 复制代码
  1. //代码1   
  2. String sa=new String("Hello world");             
  3. String sb=new String("Hello world");       
  4. System.out.println(sa==sb);  // false        
  5. //代码2     
  6. String sc="Hello world";     
  7. String sd="Hello world";   
  8. System.out.println(sc==sd);  // true   
//代码1
String sa=new String("Hello world");          
String sb=new String("Hello world");    
System.out.println(sa==sb);  // false     
//代码2  
String sc="Hello world";  
String sd="Hello world";
System.out.println(sc==sd);  // true 

       代码1中局部变量sa,sb中存储的是JVM在堆中new出来的两个String对象的内存地址。虽然这两个String对象的值(char[]存放的字符序列)都是"Hello world"。 因此"=="比较的是两个不同的堆地址。代码2中局部变量sc,sd中存储的也是地址,但却都是常量池中"Hello world"指向的堆的唯一的那个拘留字符串对象的地址 。自然相等了。

  【纷争2】 字符串“+”操作的内幕

Java代码 复制代码
  1. //代码1   
  2. String sa = "ab";                                           
  3. String sb = "cd";                                        
  4. String sab=sa+sb;                                       
  5. String s="abcd";   
  6. System.out.println(sab==s); // false   
  7. //代码2   
  8. String sc="ab"+"cd";   
  9. String sd="abcd";   
  10. System.out.println(sc==sd); //true  
//代码1
String sa = "ab";                                        
String sb = "cd";                                     
String sab=sa+sb;                                    
String s="abcd";
System.out.println(sab==s); // false
//代码2
String sc="ab"+"cd";
String sd="abcd";
System.out.println(sc==sd); //true

       代码1中局部变量sa,sb存储的是堆中两个拘留字符串对象的地址。而当执行sa+sb时,JVM首先会在堆中创建一个StringBuilder类,同时用sa指向的拘留字符串对象完成初始化,然后调用append方法完成对sb所指向的拘留字符串的合并操作,接着调用StringBuilder的toString()方法在堆中创建一个String对象,最后将刚生成的String对象的堆地址存放在局部变量sab中。而局部变量s存储的是常量池中"abcd"所对应的拘留字符串对象的地址。 sab与s地址当然不一样了。这里要注意了,代码1的堆中实际上有五个字符串对象:三个拘留字符串对象、一个String对象和一个StringBuilder对象。
      代码2中"ab"+"cd"会直接在编译期就合并成常量"abcd", 因此相同字面值常量"abcd"所对应的是同一个拘留字符串对象,自然地址也就相同。

 

 

 

 

【镜头二】  String三姐妹(String,StringBuffer,StringBuilder)
        String扒的差不多了。但他还有两个妹妹StringBuffer,StringBuilder长的也不错哦!我们也要下手了:
                           String(大姐,出生于JDK1.0时代)          不可变字符序列
                           StringBuffer(二姐,出生于JDK1.0时代)    线程安全的可变字符序列
                           StringBuilder(小妹,出生于JDK1.5时代)   非线程安全的可变字符序列

 

★StringBuffer与String的可变性问题。
         我们先看看这两个类的部分源代码:

Java代码 复制代码
  1. //String    
  2. public final class String   
  3. {   
  4.         private final char value[];   
  5.   
  6.          public String(String original) {   
  7.               // 把原字符串original切分成字符数组并赋给value[];   
  8.          }   
  9. }   
  10.   
  11. //StringBuffer    
  12. public final class StringBuffer extends AbstractStringBuilder   
  13. {   
  14.          char value[]; //继承了父类AbstractStringBuilder中的value[]   
  15.          public StringBuffer(String str) {   
  16.                  super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组   
  17.                  append(str); //将str切分成字符序列并加入到value[]中   
  18.         }   
  19. }  
//String 
public final class String
{
        private final char value[];

         public String(String original) {
              // 把原字符串original切分成字符数组并赋给value[];
         }
}

//StringBuffer 
public final class StringBuffer extends AbstractStringBuilder
{
         char value[]; //继承了父类AbstractStringBuilder中的value[]
         public StringBuffer(String str) {
                 super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组
                 append(str); //将str切分成字符序列并加入到value[]中
        }
}

      很显然,String和StringBuffer中的value[]都用于存储字符序列。但是,
      (1) String中的是常量(final)数组,只能被赋值一次。
      比如:new String("abc")使得value[]={'a','b','c'},之后这个String对象中的value[]再也不能改变了。这也正是大家常说的,String是不可变的原因 。   
      注意:这个对初学者来说有个误区,有人说String str1=new String("abc"); str1=new String("cba");不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别。这里我简单的说明一下,对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段)。而对象引用指的是堆中对象本身所存放的地址,一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据。


      (2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。

      比如:new StringBuffer("abc")使得value[]={'a','b','c','',''...}(注意构造的长度是str.length()+16)。如果再将这个对象append("abc"),那么这个对象中的value[]={'a','b','c','a','b','c',''....}。这也就是为什么大家说 StringBuffer是可变字符串 的涵义了。从这一点也可以看出,StringBuffer中的value[]完全可以作为字符串的缓冲区功能。其累加性能是很不错的,在后面我们会进行比较。

     总结,讨论String和StringBuffer可不可变。本质上是指对象中的value[]字符数组可不可变,而不是对象引用可不可变。

 

 

★StringBuffer与StringBuilder的线程安全性问题
      StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
      有多线程编程经验的程序员应该知道synchronized。这个关键字是为线程同步机制 设定的。我简要阐述一下synchronized的含义:
      每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方法。这就是解决线程同步问题的锁机制。
      了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffer比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
      注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?

 


★String和StringBuffer的效率问题(这可是个热门话题呀!)
      首先说明一点:StringBuffer和StringBuilder可谓双胞胎,StringBuilder是1.5新引入的,其前身就是StringBuffer。StringBuilder的效率比StringBuffer稍高,如果不考虑线程安全,StringBuilder应该是首选。另外,JVM运行程序主要的时间耗费是在创建对象和回收对象上。


      我们用下面的代码运行1W次字符串的连接操作,测试String,StringBuffer所运行的时间。

Java代码 复制代码
  1. //测试代码   
  2. public class RunTime{   
  3.     public static void main(String[] args){   
  4.            ● 测试代码位置1  
  5.           long beginTime=System.currentTimeMillis();   
  6.           for(int i=0;i<10000;i++){   
  7.                  ● 测试代码位置2  
  8.           }   
  9.           long endTime=System.currentTimeMillis();   
  10.           System.out.println(endTime-beginTime);      
  11.     }   
  12. }  
//测试代码
public class RunTime{
    public static void main(String[] args){
           ● 测试代码位置1
          long beginTime=System.currentTimeMillis();
          for(int i=0;i<10000;i++){
                 ● 测试代码位置2
          }
          long endTime=System.currentTimeMillis();
          System.out.println(endTime-beginTime);   
    }
}

(1) String常量与String变量的"+"操作比较
        ▲测试①代码:     (测试代码位置1)  String str="";
                                  (测试代码位置2)  str="Heart"+"Raid";
            [耗时:  0ms]
            
       ▲测试②代码        (测试代码位置1)  String s1="Heart";
                                                           String s2="Raid";
                                                           String str="";
                                  (测试代码位置2)  str=s1+s2;
            [耗时:  15—16ms]
      结论:String常量的“+连接”  稍优于  String变量的“+连接”。
      原因:测试①的"Heart"+"Raid"在编译阶段就已经连接起来,形成了一个字符串常量"HeartRaid",并指向堆中的拘留字符串对象。运行时只需要将"HeartRaid"指向的拘留字符串对象地址取出1W次,存放在局部变量str中。这确实不需要什么时间。
               测试②中局部变量s1和s2存放的是两个不同的拘留字符串对象的地址。然后会通过下面三个步骤完成“+连接”:
                                1、StringBuilder temp=new StringBuilder(s1),
                                2、temp.append(s2);
                                3、str=temp.toString();
               我们发现,虽然在中间的时候也用到了append()方法,但是在开始和结束的时候分别创建了StringBuilder和String对象。可想而知:调用1W次,是不是就创建了1W次这两种对象呢?不划算。

     但是,String变量的"+连接"操作比String常量的"+连接"操作使用的更加广泛。 这一点是不言而喻的。
    

(2)String对象的"累+"连接操作与StringBuffer对象的append()累和连接操作比较。
          ▲测试①代码:     (代码位置1)  String s1="Heart";
                                                       String s="";
                                    (代码位置2)  s=s+s1;
             [耗时:  4200—4500ms]
            
          ▲测试②代码        (代码位置1)  String s1="Heart";
                                                       StringBuffer sb=new StringBuffer();
                                    (代码位置2) sb.append(s1);
             [耗时:  0ms(当循环100000次的时候,耗时大概16—31ms)]
         结论:大量字符串累加时,StringBuffer的append()效率远好于String对象的"累+"连接
         原因:测试① 中的s=s+s1,JVM会利用首先创建一个StringBuilder,并利用append方法完成s和s1所指向的字符串对象值的合并操作,接着调用StringBuilder的 toString()方法在堆中创建一个新的String对象,其值为刚才字符串的合并结果。而局部变量s指向了新创建的String对象。

                  因为String对象中的value[]是不能改变的,每一次合并后字符串值都需要创建一个新的String对象来存放。循环1W次自然需要创建1W个String对象和1W个StringBuilder对象,效率低就可想而知了。


                  测试②中sb.append(s1);只需要将自己的value[]数组不停的扩大来存放s1即可。循环过程中无需在堆中创建任何新的对象。效率高就不足为奇了。
      

 


镜头总结:

    (1) 在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高。

    (2) StringBuffer对象的append效率要高于String对象的"+"连接操作。

    (3) 不停的创建对象是程序低效的一个重要原因。那么相同的字符串值能否在堆中只创建一个String对象那。显然拘留字符串能够做到这一点,除了程序中的字符串常量会被JVM自动创建拘留字符串之外,调用String的intern()方法也能做到这一点。当调用intern()时,如果常量池中已经有了当前String的值,那么返回这个常量指向拘留对象的地址。如果没有,则将String值加入常量池中,并创建一个新的拘留字符串对象。

分享到:
评论

相关推荐

    String和string区别以及string详解.doc

    以下是对这些知识点的详细解释: ### `String`与`string`的区别 #### 1. **位置与来源** - `String`是.NET Framework中的类,位于`System`命名空间下,而`string`是C#语言的关键字。 - 如果项目中删除了`using ...

    C语言头文件 STRING.H

    C语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING.HC语言头文件 STRING....

    StringtoList和StringtoMap和StringtoObject和StringtoArray

    Map&lt;String, String&gt; map = gson.fromJson(jsonString, new TypeToken&lt;Map&lt;String, String&gt;&gt;(){}.getType()); ``` 4. **String to Object** 如果JSON字符串代表的是一个自定义Java对象,你可以创建一个对应的类...

    C#中char[]与string之间的转换 string 转换成 Char[]

    C#中char[]与string之间的转换 C#中char[]与string之间的转换是一种常见的操作,我们经常需要在这两种数据类型之间进行转换。今天,我们将探讨C#中char[]与string之间的转换,包括string转换成Char[]和Char[]转换成...

    List转换成String数组

    ### List转换成String数组 在Java编程中,我们经常需要对集合进行操作,尤其是在处理大量字符串数据时。本文将详细介绍如何将一个`List&lt;String&gt;`类型的集合转换为`String[]`数组,并通过不同的方法来实现这一过程。...

    HexString和Base64String的相互转换

    `HexString`和`Base64String`是两种常见的二进制数据的文本表示形式。理解它们的特性和转换方法对于开发人员来说是非常基础且实用的知识。 首先,`HexString`(十六进制字符串)是一种将二进制数据表示为十六进制...

    C# String 的各种转换

    ### C# String 的各种转换 在C#编程语言中,字符串与数字之间的转换是非常常见的操作。本文将详细介绍如何在C#中实现字符串与其他数据类型(如整数、浮点数等)之间的转换,并特别关注字符串与十六进制之间的转换。...

    string和char*

    string 和 char* 的区别和联系 在 C++ 编程中,字符串是一种常用的数据类型,string、CString 和 char*都是字符串的 представители,每种类型都有其特点和使用场景。下面我们将详细介绍 string、...

    public static String[] split(String s, String regex)

    public static String[] split(String s, String regex) s参数为待拆分字符串, regex参数有两种格式: 单字符的字符串(长度1),功能如下:split(“ab#12#453”, “#”) 返回带5个元素的数组:ab, #, 12, #, 453 ...

    String和string区别以及string详解

    本文将深入探讨`String`和`string`的区别,并对`string`类型进行详尽的解释。 首先,`String`和`string`在C#中实际上是指同一个东西,都是System.String类的别名。C#设计者为了提高代码的可读性,推荐在编写代码时...

    StringAPI.java

    Java String 类型 API 测试代码 1.String和char[]之间的转换 toCharArray(); 2.String和byte[]之间的转换 getBytes() Arrays工具类 : Arrays.toString(names) String类 String replace(char oldChar, ...

    String对象创建问题

    在Java编程语言中,`String`对象的创建是开发者经常遇到的问题,因为它涉及到内存管理和效率。`String`类在Java中被广泛使用,因为它代表不可变的字符序列,这使得它在很多场景下非常安全。这里我们将深入探讨`...

    String[] list 相互转化

    ### String[] 与 List 相互转化 在 Java 编程语言中,`String[]` 数组和 `List` 集合之间的相互转换是非常常见的需求。这两种数据结构各有优势:数组提供了固定长度且访问效率高的特性,而列表则支持动态调整大小...

    java基础String类选择题练习题

    - **知识点**:解释为什么`String`类是不可变的(immutable),以及不可变性带来的好处。 - **例题**:选择题中可能会问到关于字符串池的概念以及如何利用这一点来提高程序性能。 #### 2. 字符串比较 - **知识点**...

    C#_String与string的区别

    ### C#中String与string的区别详解 在C#编程语言中,`String`与`string`两者虽然在表面上看起来相似,但它们之间存在着细微而重要的差异。这些差异主要体现在它们的定义、使用场合以及编译过程中的处理方式上。本文...

    list转换成string数组

    ### List转换成String数组 在Java编程语言中,经常需要将`List&lt;String&gt;`类型的数据转换为`String[]`数组类型,以便于进行某些特定的操作或适应某些方法的要求。本文将详细探讨这一转换过程,并通过几个具体的示例来...

    JavaBean与JsonString的相互转换

    在Java开发中,JavaBean和JsonString是两种常见的数据表示形式。JavaBean是Java对象的一种规范,用于封装数据,而JsonString是一种轻量级的数据交换格式,常用于前后端交互。本篇将深入探讨JavaBean与JsonString之间...

    stringstream操纵string的方法总结

    1 split字符串 之前在用C#写代码的时候,用过split函数,可以把一个字符串根据...string inputString(/home/fun/./../code/); stringstream ss(inputString); string tmp; while(getline(ss,tmp,'/')) { if(tmp.empty

    c++中double与string相互转换算法

    本文将详细讨论如何在C++中将`double`类型的数值转换为`std::string`字符串,以及如何将`std::string`转换回`double`。我们将基于提供的`stringtodouble`工程文件进行讨论。 首先,让我们探讨`double`转`string`的...

    基于Keil实现字符串函数string.h的简单应用

    基于Keil实现字符串函数string.h的简单应用基于Keil实现字符串函数string.h的简单应用基于Keil实现字符串函数string.h的简单应用基于Keil实现字符串函数string.h的简单应用基于Keil实现字符串函数string.h的简单应用...

Global site tag (gtag.js) - Google Analytics