由于日常见到的大部分文本都是基于字符串的,所以Java也提供了强大的API支持,大大方便了字符串的处理。下面就来具体看一下Java源代码是如何来处理这些字符串的。
首先来看如下的一题:
1、Strings=newString("xyz");创建了几个String 对象?
3、String name = "ab"; name = name + "c";两条语句总共创建了多少个字符串对象?
创建了两个对象,这两个对象都会放到缓存池中,只是name的引用由"ab"改变为"abc"了。我们在这样用的时候,还需要考虑其他问题,如这个程序会造成内在泄漏,因为缓存池中的在缓存池中的字符串是不会被垃圾回收机制回收的,基本都是常驻内存,所以过多使用String类,可能会出现内存溢出。
4、
String s1 = "a";
String s2 = s1 + "b";
String s3 = "ab";
System.out.println(s2 == s3);//false
可以看到s2与s3的引用并不相同。由于s2字符串在编译时并不能进行确定,所以首先进入缓存池中的有s1和s3,随后会创建一个新的s2字符串对象,两者当然不一样了。
如果程序的字符串连接表达式中没有使用变量或者调用方法,那么该字符串变量的值就能够在编译期间确定下来,并且将该字符换缓存在缓冲区中,同时让该变量指向该字符串;否则将无法利用缓冲区,因为使用了变量和调用了方法之后的字符串变量的值只能在运行期间才能确定连接式的值,也就无法在编译期间确定字符串变量的值,从而无法将字符串变量增加到缓冲区并加以利用。
所以如何有字符串拼接之类的操作,建议使用线程安全的StringBuilder类型或StringBuffer类。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
private final char value[]; //用来存储字符串转换而来的字符数组
private final int offset; //字符串起始字符在字符数组的位置
private final int count; //字符串分解成字符数组后字符的数目
}
从上面代码,我们知道:
1> String类是被final修饰的,从安全角度来说,通过final修饰后的String类是不能被其他类继承的,在最大程度上的保护了该类,从效率上来说,提高了该类的效率,因为final修饰后会比没有用final修饰的快。
2> value[], offet, count也是被final修饰的,这时候三个变量的值必须在编译期间就被确定下来,并且在运行期间不能再被修改了。因此,每次我们每次进行字符串修改、拼接的时候,并不能直接修改当前String对象的值,只能重新创建一个新的对象。
3>我们创建String对象的时候,String对象还使用字符数组(char[])来存储我们的字符串的。
下面来看一下String类的源代码,首先来看String类最常用的一个构造函数,如下:
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.
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+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;
}
使用如上的构造函数也就允许我们通过String x=new String("字符串")的形式来创建一个字符串,但是通常不建议这样做,前面讲到过,可能会创建两个字符串对象,消耗太大。继续来看其他的一些常用构造函数
public String(char value[]) {// 传入字符数组返回字符串
int size = value.length;
this.offset = 0;
this.count = size;
this.value = Arrays.copyOf(value, size);// copy the newly string
}
// 指定范围内的字符数组组成字符串
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);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the Unicode code point array
* argument.
*/
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);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char) c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
this.count = n;
this.offset = 0;
}
编写一个测试程序,如下:
char data[] = {'a', 'b', 'c'};
String str1 = new String(data);
String str2 = new String(data,0,2);
char data1[] = {0x4E2D, 0x56FD};
String str3 = new String(data1);
System.out.println(str1);//abc
System.out.println(str2);//ab
System.out.println(str3);//中国
还有以byte[]数组为参数创建字符串的构造函数,其中最主要的两个如下: 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;
}
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;
}
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
char[] v = StringCoding.decode(charset, bytes, offset, length);
this.offset = 0;
this.count = v.length;
this.value = v;
}
编写测试程序如下:
byte[] ascBytes = {(byte)0x61, (byte)0x62, (byte)0x63}; // ASCII的'a','b','c'字符
System.out.println(new String(ascBytes,0,2));//abc
System.out.println(new String(ascBytes,0,2,Charset.forName("ISO-8859-1")));//abc
System.out.println(new String(ascBytes,0,2,"ISO-8859-1"));//abc
其他的一些使用byte数组创建字符串的构造函数其实最终都是调用如上两个构造函数。另外还提供了StringBuffer和StringBuilder转String字符串的构造函数,非常简单,这里不做介绍。
接下来就看一看String常用的API了。
public int length() {
return count;
}
返回字符串的长度,但是需要注意的是:
String类中的length()方法也是对字符串进行char统计,也就是计算代码单元数量(代码单元有可能大于正真的字符数量,如果字符串中有增补字符的话)。如果要计算代码点数量,必须用str.codePointCount(0,str.length())方法。
由此可见,用char类型来处理字符串在有些情况下是不准确的,我们最好使用字符串类型中处理代码点的方法,不要使用处理char类型(一个代码单元)的方法。
public native String intern();
这是一个本地的方法,当调用 intern 方法时,如果缓存池已经包含一个等于此 String 对象的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。String param = "abc";
String newStr = new String("abc");
String param2 = new String("abc");
newStr.intern();
param2 = param2.intern(); //param2指向intern返回的常量池中的引用
System.out.println(param == newStr); //false
System.out.println(param == param2); //true
可以看到,使用intern()方法后,字符串变量的值如果不存在缓冲区中将会被缓存