java学习笔记24——String类不可变性

来源:互联网 发布:数控车床左牙怎么编程 编辑:程序博客网 时间:2024/05/19 11:47
如果我们需要修改一个String对象,最好使用StringBuffer或StringBuilder。否则,会在每次创建一个新的String对象时消耗垃圾回收器大量时间。

How to read file content into a string?

The following is the Java code to do that. To make it work, the filePath need to be changed.

public static String readFileToString() throws IOException {File dirs = new File(".");String filePath = dirs.getCanonicalPath() + File.separator+"src"+File.separator+"TestRead.java"; StringBuilder fileData = new StringBuilder(1000);//Constructs a string buffer with no characters in it and the specified 
                                                                  //initial capacityBufferedReader reader = new BufferedReader(new FileReader(filePath)); char[] buf = new char[1024];int numRead = 0;while ((numRead = reader.read(buf)) != -1) {String readData = String.valueOf(buf, 0, numRead);fileData.append(readData);buf = new char[1024];} reader.close(); String returnStr = fileData.toString();System.out.println(returnStr);return returnStr;}


        By saying String, it is not exactly correct, it is actually called a StringBuffer in Java. Here is a post about Java String’s immutability. StringBuffer and StringBuilder are similar and they both are are for mutable strings. But StringBuffer is safe for use by multiple threads. The reason to use StringBuilder instead of StringBuffer here is that StringBuilder is faster, as it performs no synchronization.

        From programming usage perspective, String, StringBuffer, StringBuilder are all strings.

        上面英文是复制的一个老外的博客,没有校正。

        首先,必须强调一点:String Pool不是在堆区,也不是在栈区,而是存在于方法区(Method Area)

        解析:

        String Pool是常量池(Constant Pool)中的一块。

        我们知道,常量就是不可以再改变的值,给它建一个池子很明显是为了加快程序运行的速度;在一个程序中,常量和变量是相对存在的;变量因为可变性所以一般存在于栈中,而常量去作为一个特殊群体被存在在常量池中。

        常量池(constant pool)指的是在编译期被确定并被保存在已编译的.class文件中的一些数据。--- (很明显在方法区)

        它包括了关于类、方法、接口等中的常量,也包括字符串常量(这个就是Sring Pool啦)。

        在编译好的class文件中,有个区域称为Constant Pool,它是一个由数组组成的表,类型为cp_info constant_pool[],用来存储程序中使用的各种常量,包括Class/String/Integer等各种基本Java数据类型。

======= ================

        上面这些,简单理解:一个Class类,它里面有常量的存在,比如 int a=10;String b="123450";它们在JVM看来就是常量(当然在方法中可能被修改啦),在Class被加载时,JVM特意都把它放在一个数组中维护起来,并且把该数组放在方法区中,起名叫常量池。

 

        常量池存在于方法区,它包含各种类型的常量(8个基本数据类型,包装类型等)

        我们把常量池中的String Pool中的常量作为对象来看待 --- 因为String就是对象,String类型的常量自然也是对象啦!

比如:

        String str1 = new String("Hello");

        它创建了2个对象,一个是堆中的String对象,一个是String Pool中的String对象。

        要理解Java中String的运作方式,必须明确一点:String是一个非可变类(immutable)。什么是非可变类呢?简单说来,非可变类的实例是不能被修改的,每个实例中包含的信息都必须在该实例创建的时候就提供出来,并且在对象的整个生存周期内固定不变。Java为什么要把String设计为非可变类呢?你可以问问 james Gosling :)。但是非可变类确实有着自身的优势,如状态单一,对象简单,便于维护。其次,该类对象对象本质上是线程安全的,不要求同步。此外用户可以共享非可变对象,甚至可以共享它们的内部信息。(详见 《Effective java》item 13)。String类在java中被大量运用,甚至在class文件中都有其身影,因此将其设计为简单轻便的非可变类是比较合适的。

一、创建。
    好了,知道String是非可变类以后,我们可以进一步了解String的构造方式了。创建一个Stirng对象,主要就有以下两种方式:
java 代码
  1. String str1 = new String("abc");   //只是在堆里面创建而不会添加到常量池  
  2. Stirng str2 = "abc";   //先用equals在常量池查找,有就返回给用户,没有就在堆里创建,然后添加到常量池。

        虽然两个语句都是返回一个String对象的引用,但是jvm对两者的处理方式是不一样的。对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。对于第二种,jvm首先会在内部维护的String Pool中通过String的 equals 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至String Pool中。注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到String Pool
里面的,除非程序调用 String的intern方法。看 下面的例子:

java 代码
  1. String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  2.   
  3.  //jvm 在strings pool中找不到值为“abc”的字符串,因此   
  4.  //在堆上创建一个String对象,并将该对象的引用加入至strings pool中   
  5.  //此时堆上有两个String对象   
  6. Stirng str2 = "abc";   
  7.   
  8.  if(str1 == str2){   
  9.          System.out.println("str1 == str2");   
  10.  }
  11.  else{   
  12.          System.out.println("str1 != str2");   
  13.  }   
  14.   //打印结果是 str1 != str2,因为它们是堆上两个不同的对象   
  15.   
  16.   String str3 = "abc";   
  17.  //此时,jvm发现String Pool中已有“abc”对象了,因为“abc”equals “abc”   
  18.  //因此直接返回str2指向的对象给str3,也就是说str2和str3是指向同一个对象的引用   
  19.   if(str2 == str3){   
  20.          System.out.println("str2 == str3");   
  21.   }
  22.   else{   
  23.          System.out.println("str2 != str3");   
  24.   }   
  25.  //打印结果为 str2 == str3  

   再看下面的例子:

java 代码
  1. String str1 = new String("abc"); //jvm 在堆上创建一个String对象   
  2.   
  3. str1 = str1.intern();   
  4. //程序显式将str1放到String Pool中,intern运行过程是这样的:首先查看String Pool   
  5. //有没“abc”对象的引用,没有,则在堆中新建一个对象,然后将新对象的引用加入至   
  6. //String Pool中。执行完该语句后,str1原来指向的String对象已经成为垃圾对象了,随时会   
  7. //被GC收集。   
  8.   
  9. //此时,jvm发现String Pool中已有“abc”对象了,因为“abc”equals “abc”   
  10. //因此直接返回str1指向的对象给str2,也就是说str2和str1引用着同一个对象,   
  11. //此时,堆上的有效对象只有一个。   
  12. Stirng str2 = "abc";   
  13.   
  14.  if(str1 == str2){   
  15.          System.out.println("str1 == str2");   
  16.  }
  17.  else{   
  18.          System.out.println("str1 != str2");   
  19.  }   
  20.   //打印结果是 str1 == str2   
  21.   

 

    为什么jvm可以这样处理String对象呢?就是因为String的非可变性。既然所引用的对象一旦创建就永不更改,那么多个引用共用一个对象时互不影响。


二、串接(Concatenation)。
     java程序员应该都知道
滥用String的串接操作符是会影响程序的性能的。性能问题从何而来呢?归根结底就是String类的非可变性。既然String对象都是非可变的,也就是对象一旦创建了就不能够改变其内在状态了,但是串接操作明显是要增长字符串的,也就是要改变String的内部状态,两者出现了矛盾。怎么办呢?要维护String的非可变性,只好在串接完成后新建一个String 对象来表示新产生的字符串了。也就是说,每一次执行串接操作都会导致新对象的产生,如果串接操作执行很频繁,就会导致大量对象的创建,性能问题也就随之而来了。
    为了解决这个问题,jdk为String类提供了一个可变的配套类,StringBuffer。使用StringBuffer对象,由于该类是可变的,串接时仅仅时改变了内部数据结构,而不会创建新的对象,因此性能上有很大的提高。针对单线程,jdk 5.0还提供了StringBuilder类,在单线程环境下,由于不用考虑同步问题,使用该类使性能得到进一步的提高。

三、String的长度
   我们可以使用串接操作符得到一个长度更长的字符串,那么,String对象最多能容纳多少字符呢?查看String的源代码我们可以得知类String中是使用域 count 来记录对象字符的数量,而count 的类型为 int,因此,我们可以推测最长的长度为 2^32,也就是
4G
    不过,我们在编写源代码的时候,如果使用 Sting str = "aaaa";的形式定义一个字符串,那么双引号里面的ASCII字符最多只能有 65534 个。为什么呢?因为在class文件的规范中, CONSTANT_Utf8_info表中使用一个16位的无符号整数来记录字符串的长度的,最多能表示 65536个字节,而java class 文件是使用一种变体UTF-8格式来存放字符的,null值使用两个字节来表示,因此只剩下 65536- 2 = 65534个字节。也正是变体UTF-8的原因,如果字符串中含有中文等非ASCII字符,那么双引号中字符的数量会更少(一个中文字符占用三个字节)。如果超出这个数量,在编译的时候编译器会报错。


0 0
原创粉丝点击