关注Java细节 -- String类 (一)

在工作过程中,我越来越发现自己对于Java的很多细节还不是非常的了解,所以打算花些了解一些Java的细节,并将其总结出来,希望能对大家有帮助。

 

首先,我们就从String类开始,在这一篇中,我将主要介绍String类的构造函数以及String类的成员。

 

在JDK1.5中,给String类提供了如下构造函数:

1. public String(); 2. public String(String original); 3. public String(char value[]); 4. public String(char value[], int offset, int count); 5. public String(int[] codePoints, int offset, int count); 6. public String(byte ascii[], int hibyte, int offset, int count); @Deprecated 7. public String(byte ascii[], int hibyte); @Deprecated 8. public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException; 9. public String(byte bytes[], String charsetName) throws UnsupportedEncodingException; 10. public String(byte bytes[], int offset, int length); 11. public String(byte bytes[]); 12. public String(StringBuffer buffer); 13. public String(StringBuilder builder); 14. String(int offset, int count, char value[]);

 

 

我们可以看到,6,7两个构造函数已经被JDK1.5标示为@Deprecated,也就是说这两个构造函数已经是不被推荐的了,所以我们在此也就不对其进行描述了。

 

在详细介绍上面提到的构造函数之前,让我们一起来看看String类的成员:

 

/** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written initially into an ObjectOutputStream in the * following format: * <pre> * <code>TC_STRING</code> (utf String) * </pre> * The String is written by method <code>DataOutput.writeUTF</code>. * A new handle is generated to refer to all future references to the * string instance within the stream. */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; /** * A Comparator that orders <code>String</code> objects as by * <code>compareToIgnoreCase</code>. This comparator is serializable. * <p> * Note that this Comparator does <em>not</em> take locale into account, * and will result in an unsatisfactory ordering for certain locales. * The java.text package provides <em>Collators</em> to allow * locale-sensitive ordering. * * @see java.text.Collator#compare(String, String) * @since 1.2 */ public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();   

 

JDK给的注释非常简单明白,我这也不再多加叙述。

有一点需要说明的是,char数组value的长度可以与String类中的字符数count不一致。

也就是说String类可能会保持多余的字符,但是在对外提供服务时,对外显露的字符是通过offset和count来决定的,其他的字符将被隐藏起来,不可见。至于这么做的好处,我现在也没有搞得太明白,等我搞明白了,会在后续的文章中向大家说明。

 

另外,CaseInsensitiveComparator类是一个在String类里面定义的私有静态内部类,实现了Comparator接口,其定义如下:

 

private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1=s1.length(), n2=s2.length(); for (int i1=0, i2=0; i1<n1 && i2<n2; i1++, i2++) { char c1 = s1.charAt(i1); char c2 = s2.charAt(i2); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { return c1 - c2; } } } } return n1 - n2; } }

好的,下面我们就来看看String类的构造函数:

1. public String()

1. public String() { this.offset = 0; this.count = 0; this.value = new char[0]; }

在这个无参构造函数中,简单的创建了一个空字符串。

用JDK中的原话来说这个构造函数就是:Note that use of this constructor is unnecessary since Strings are immutable。

 

2. public String(String original)

public String(String original){ int size = original.count; char[] originalValue = original.value; char[] v; if (originalValue.length > size) { // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array. v = new char[size]; System.arraycopy(originalValue, original.offset, v, 0, size); } else { // The array representing the String is the same // size as the String, so no point in making a copy. v = originalValue; } this.offset = 0; this.count = size; this.value = v; }

用C++的话来说,这个就是String类的拷贝构造函数。

从代码中我们可以看出,在拷贝构造函数中,当源String类中存在垃圾数据时(这里,我们暂且把那些多余的不向外显露的数据叫做垃圾数据),在拷贝的过程中,将过滤掉垃圾数据。如果源String类中没有垃圾数据的话,就直接赋值。

这里我们看到,在拷贝的过程中,用到了 System.arraycopy(originalValue, original.offset, v, 0, size);
System是一个final类,它的构造函数是private的,因此不能被实例化。但是它提供了很多static的方法。

arraycopy是其中的一个方法,提供了数组对象之间的内容拷贝功能,其定义如下:

/** * * @param src the source array. * @param srcPos starting position in the source array. * @param dest the destination array. * @param destPos starting position in the destination data. * @param length the number of array elements to be copied. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception ArrayStoreException if an element in the <code>src</code> * array could not be stored into the <code>dest</code> array * because of a type mismatch. * @exception NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);   

 

3. public String(char value[])

public String(char value[]) { int size = value.length; char[] v = new char[size]; System.arraycopy(value, 0, v, 0, size); this.offset = 0; this.count = size; this.value = v; }

这个构造函数以一个字符数组作为入参,根据字符数组中的内容构造String对象。

 

4. public String(char value[], int offset, int count)

public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } char[] v = new char[count]; System.arraycopy(value, offset, v, 0, count); this.offset = 0; this.count = count; this.value = v; }

这个构造函数是上一个构造函数的一个增强版,可以截取入参数组的一部分来构造String对象。
具体截取哪一部分由入参中的offset以及count共同决定。

 

5. public String(int[] codePoints, int offset, int count) 
   

public String(int[] codePoints, int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near -1>>>1. if (offset > codePoints.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } int expansion = 0; int margin = 1; char[] v = new char[count + margin]; int x = offset; int j = 0; for (int i = 0; i < count; i++) { int c = codePoints[x++]; if (c < 0) { throw new IllegalArgumentException(); } if (margin <= 0 && (j+1) >= v.length) { if (expansion == 0) { expansion = (((-margin + 1) * count) << 10) / i; expansion >>= 10; if (expansion <= 0) { expansion = 1; } } else { expansion *= 2; } char[] tmp = new char[Math.min(v.length+expansion, count*2)]; margin = (tmp.length - v.length) - (count - i); System.arraycopy(v, 0, tmp, 0, j); v = tmp; } if (c < Character.MIN_SUPPLEMENTARY_CODE_POINT) { v[j++] = (char) c; } else if (c <= Character.MAX_CODE_POINT) { Character.toSurrogates(c, v, j); j += 2; margin--; } else { throw new IllegalArgumentException(); } } this.offset = 0; this.value = v; this.count = j; }


这个构造函数跟上一个构造函数的原理是一样的,不同的是这里的入参是一个codePoint数组。
在构造函数中,将codePoint转换成对应的char,然后赋值给String对象的value。

关于什么是codePoint,我在网上找了一个大致的介绍,希望对大家有帮助:
----------------------
Unicode 是一个勇敢的成就。它把在这个星球上的每一个合理的文字系统整合成了一个单一的字符集。
很多人还存在这样的误解: Unicode 仅仅是 16 位的这么简单,每个字符占 16 位,所以一共有 65536 个可能的字符。
然而,这是错误的。不过不要紧,因为这是大部分人都会犯的一个普遍的错误。

实际上,Unicode 理解字符的方式是截然不同的,而这是我们必须了解的。
到目前为止,我们都曾经认为:一个字符对应到一些在磁盘上或内存中储存的位(bits). 如: A -> 0100 0001
而在 Unicode 中, 一个字符实际上对应一种叫做 code point 的东西。
比如 A 这个字符,是抽象的(原文:platonic,柏拉图式的,理想的)一个概念。
无论是 Times New Roman 或者 Helvetica 或者其他的什么字体中,都代表同一个字符。但是它和小写的字母 a 不同。
但是在其他的语言,比如希伯莱语(Hebrew) 或者德语(German), 阿拉伯语(Arabian) 中,同一个字母的不同的字形代表的含义是否
相同,是有争议的。经过长时间的争论,这些也终于被确定了。

每一个字母表中的每一个抽象的字母,都被赋予了一个数字,比如 U+0645. 这个叫做 code point.
U+ 表示: Unicode, 数字是 16 进制的。
你可以通过 charmap 命令来查看所有这些编码。(Windows 2000/XP 中). 或者访问 Unicode 的网站(http://www.unicode.org)
Unicode 中 code point 的数字的大小是没有限制的,而且也早就超过了 65535. 所以不是每个字符都能存储在两个字节中。
那么,一个字符串 "Hello", 在 Unicode 中会表示成 5 个 code points :
  U+0048 U+0065 U+006C U+006C U+006F
----------------------

 

8. public String(byte bytes[], int offset, int length, String charsetName)
 throws UnsupportedEncodingException

public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException{ if (charsetName == null) throw new NullPointerException("charsetName"); checkBounds(bytes, offset, length); char[] v = StringCoding.decode(charsetName, bytes, offset, length); this.offset = 0; this.count = v.length; this.value = v; }   

 

在这个构造函数中,将入参byte数组中的指定内容根据charsetName指定的字符集名称来进行转换,并将结果存放到String对象的成员value中。

 

另外,在这段函数中我们看到了一个函数: checkBounds(bytes, offset, length);

这个函数是String类的一个内部私有工具函数,提供了数组相关参数的合法性检查。其源代码如下:

 

private static void checkBounds(byte[] bytes, int offset, int length) { if (length < 0) throw new StringIndexOutOfBoundsException(length); if (offset < 0) throw new StringIndexOutOfBoundsException(offset); if (offset > bytes.length - length) throw new StringIndexOutOfBoundsException(offset + length); }

 

9. public String(byte bytes[], String charsetName)  throws UnsupportedEncodingException

 

public String(byte bytes[], String charsetName) throws UnsupportedEncodingException{ this(bytes, 0, bytes.length, charsetName); }

 

这个构造函数利用了上一个构造函数的实现,将入参byte数组中的所有内容根据charsetName指定的字符集名称来进行转换,并将结果存放到String对象的成员value中。

 

10. public String(byte bytes[], int offset, int length)

 

 public String(byte bytes[], int offset, int length){ checkBounds(bytes, offset, length); char[] v = StringCoding.decode(bytes, offset, length); this.offset = 0; this.count = v.length; this.value = v; }

 

这个函数和第8个构造函数很类似,实现了相同的功能,只不过是少了一个入参:charsetName。

在这种情况下,会使用当前系统默认的字符集来对输入的字符数组中的指定内容进行编码并赋值给String对象的value成员。

 

11. public String(byte bytes[])

 

 public String(byte bytes[]){ this(bytes, 0, bytes.length); }

 

该构造函数使用了第10个构造函数来实现自己,使用当前系统默认的字符集来对输入的字符数组中的所有内容进行编码并赋值给String对象的value成员。

 

12. public String(StringBuffer buffer)

 

public String(StringBuffer buffer){ String result = buffer.toString(); this.value = result.value; this.count = result.count; this.offset = result.offset; }

 

该构造函数根据入参StringBuffer对象中的字符内容来构建新的String对象。

 

13. public String(StringBuilder builder)

 

 public String(StringBuilder builder){ String result = builder.toString(); this.value = result.value; this.count = result.count; this.offset = result.offset; }

 

该构造函数的功能和第12个完全一样,只不过入参是一个StringBuilder对象。

 

这里顺便提一下StringBuffer类和StringBuilder类的区别:StringBuffer类是线程安全的,而StringBuilder不是线程安全的,其实StringBuilder就是StringBuffer的一个简单实现,他们有着相同的对外接口。

 

14. String(int offset, int count, char value[])

 

String(int offset, int count, char value[]){ this.value = value; this.offset = offset; this.count = count; }

 

这是一个包内私有的构造函数。通过直接共享字符数组,而不进行截取的方式来加快运行速度。

说白了,也就是用空间换时间了。这可能就是String对象中可能出现垃圾数据的原因之一。

 

好的,上面我们已经大致把String类的所有构造函数都过了一遍。在看之前,我也没有想到,String类竟然提供了这么多的构造函数,可能我们平时用到的也就是其中的几个,不过了解String类的构造函数之后,可能会对我们之后编程中,关于String类的构造方式的选择有更好的了解。

 

在后面的文章中,我们再来一起了解String类提供的一些方法的功能和实现。

 

 

 

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

作者:  Derek Jiang

email:  [email protected]

 

 

你可能感兴趣的:(关注Java细节 -- String类 (一))