天天看点

关于String的长度限制

要理解 java 中String的运作方式,必须明确一点:String 是一个非可变类(immutable)。

什么是非可变类呢?简单说来,非可变类的实例是不能被修改的,每个实例中包含

的信息都必须在该实例创建的时候就提供出来,并且在对象的整个生存周期内固定

不变。java 为什么要把String 设计为非可变类呢?你可以问问 james Gosling :)。但

是非可变类确实有着自身的优势,如状态单一,对象简单,便于维护。其次,该类

对象对象本质上是线程安全的,不要求同步。此外用户可以共享非可变对象,甚至

可以共享它们的内部信息。(详见《Effective java》item 13)。String 类在java 中被大

量运用,甚至在class 文件中都有其身影,因此将其设计为简单轻便的非可变类是比

较合适的。

一、创建。

好了,知道String 是非可变类以后,我们可以进一步了解String 的构造方式了。

创建一个Stirng 对象,主要就有以下两种方式:

java 代码

String str1 = new String("abc");

Stirng str2 = "abc";

虽然两个语句都是返回一个String 对象的引用,但是jvm 对两者的处理方式是

不一样的。对于第一种,jvm 会马上在heap 中创建一个String 对象,然后将该对象

的引用返回给用户。对于第二种,jvm 首先会在内部维护的strings pool 中通过String

的 equels 方法查找是对象池中是否存放有该String 对象,如果有,则返回已有的

String 对象给用户,而不会在heap 中重新创建一个新的String 对象;如果对象池中

没有该String 对象,jvm 则在heap 中创建新的String 对象,将其引用返回给用户,

同时将该引用添加至strings pool 中。注意:使用第一种方法创建对象时,jvm 是不

会主动把该对象放到strings pool 里面的,除非程序调用 String 的intern 方法。看下

面的例子:

java 代码

String str1 = new String("abc"); //jvm 在堆上创建一个String 对象

//jvm 在strings pool 中找不到值为“abc”的字符串,因此

//在堆上创建一个String 对象,并将该对象的引用加入至strings pool 中

//此时堆上有两个String 对象

Stirng str2 = "abc";

if(str1 == str2){

System.out.println("str1 == str2");

}else{

System.out.println("str1 != str2");

}

//打印结果是 str1 != str2,因为它们是堆上两个不同的对象

String str3 = "abc";

//此时,jvm 发现strings pool 中已有“abc”对象了,因为“abc”equels “abc”

//因此直接返回str2 指向的对象给str3,也就是说str2 和str3 是指向同一个对象

的引用

if(str2 == str3){

System.out.println("str2 == str3");

}else{

System.out.println("str2 != str3");

}

//打印结果为 str2 == str3

再看下面的例子:

java 代码

String str1 = new String("abc"); //jvm 在堆上创建一个String 对象

str1 = str1.intern();

//程序显式将str1 放到strings pool 中,intern 运行过程是这样的:首先查看

strings pool

//有没“abc”对象的引用,没有,则在堆中新建一个对象,然后将新对象的引用

加入至

//strings pool 中。执行完该语句后,str1 原来指向的String 对象已经成为垃圾对

象了,随时会

//被GC 收集。

//此时,jvm 发现strings pool 中已有“abc”对象了,因为“abc”equels “abc”

//因此直接返回str1 指向的对象给str2,也就是说str2 和str1 引用着同一个对象,

//此时,堆上的有效对象只有一个。

Stirng str2 = "abc";

if(str1 == str2){

System.out.println("str1 == str2");

}else{

System.out.println("str1 != str2");

}

//打印结果是 str1 == str2

为什么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 字符,那么

双引号中字符的数量会更少(一个中文字符占用三个字节)。如果超出这个数量,在

编译的时候编译器会报错。